Skip to content

Commit b58f261

Browse files
Lickem22 user management (#455)
- Add new user management page for admins - Fix UX functionality - logged in admin should not be able to un-admin themselves - Don't show user management page to non-admins --------- Co-authored-by: amir_emami <amirali1376@gmail.com>
1 parent 4b9e420 commit b58f261

File tree

20 files changed

+1178
-300
lines changed

20 files changed

+1178
-300
lines changed

.secrets.baseline

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,15 @@
145145
"line_number": 62
146146
}
147147
],
148+
".github/workflows/deploy_gcp_core_backend.yaml": [
149+
{
150+
"type": "Secret Keyword",
151+
"filename": ".github/workflows/deploy_gcp_core_backend.yaml",
152+
"hashed_secret": "14a9a30abdb4f24769083489080470ca78f002f6",
153+
"is_verified": false,
154+
"line_number": 64
155+
}
156+
],
148157
".github/workflows/deploy_gcp_litellm_proxy.yaml": [
149158
{
150159
"type": "Secret Keyword",
@@ -455,7 +464,7 @@
455464
"filename": "core_backend/tests/api/test_user_tools.py",
456465
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
457466
"is_verified": false,
458-
"line_number": 47
467+
"line_number": 70
459468
}
460469
],
461470
"core_backend/tests/rails/test_language_identification.py": [
@@ -572,5 +581,5 @@
572581
}
573582
]
574583
},
575-
"generated_at": "2024-11-15T14:09:33Z"
584+
"generated_at": "2024-11-13T20:05:41Z"
576585
}

admin_app/src/app/integrations/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import React, { useState } from "react";
55
import { format } from "date-fns";
66
import { Layout } from "@/components/Layout";
77
import { appColors, sizes } from "@/utils";
8-
import { apiCalls } from "@/utils/api";
98
import { createNewApiKey } from "./api";
109
import { useAuth } from "@/utils/auth";
1110

1211
import { KeyRenewConfirmationModal, NewKeyModal } from "./components/APIKeyModals";
1312
import ConnectionsGrid from "./components/ConnectionsGrid";
1413
import { LoadingButton } from "@mui/lab";
14+
import { getUser } from "../user-management/api";
1515

1616
const IntegrationsPage = () => {
1717
const [currAccessLevel, setCurrAccessLevel] = React.useState("readonly");
@@ -59,7 +59,7 @@ const KeyManagement = ({
5959
const setApiKeyInfo = async () => {
6060
setKeyInfoFetchIsLoading(true);
6161
try {
62-
const data = await apiCalls.getUser(token!);
62+
const data = await getUser(token!);
6363
setCurrentKey(data.api_key_first_characters);
6464
const formatted_api_update_date = format(
6565
data.api_key_updated_datetime_utc,
Lines changed: 11 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,21 @@
1-
import CheckIcon from "@mui/icons-material/Check";
2-
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
3-
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
41
import {
5-
Alert,
6-
Avatar,
7-
Box,
82
Button,
93
Dialog,
104
DialogActions,
115
DialogContent,
126
DialogContentText,
137
DialogTitle,
14-
TextField,
15-
Typography,
168
} from "@mui/material";
17-
import React, { useState } from "react";
18-
19-
interface User {
20-
username: string;
21-
recovery_codes: string[];
22-
}
23-
interface RegisterModalProps {
24-
open: boolean;
25-
onClose: (event: {}, reason: string) => void;
26-
onContinue: (recoveryCodes: string[]) => void;
27-
registerUser: (username: string, password: string) => Promise<User>;
28-
}
29-
30-
function RegisterModal({
31-
open,
32-
onClose,
33-
onContinue,
34-
registerUser,
35-
}: RegisterModalProps) {
36-
const [username, setUsername] = useState("");
37-
const [password, setPassword] = useState("");
38-
const [confirmPassword, setConfirmPassword] = useState("");
39-
const [errorMessage, setErrorMessage] = useState("");
40-
const [errors, setErrors] = React.useState({
41-
username: false,
42-
password: false,
43-
confirmPassword: false,
44-
confirmPasswordMatch: false,
45-
});
46-
const validateForm = () => {
47-
const newErrors = {
48-
username: username === "",
49-
password: password === "",
50-
confirmPassword: confirmPassword === "",
51-
confirmPasswordMatch: password !== confirmPassword,
52-
};
53-
setErrors(newErrors);
54-
return Object.values(newErrors).every((value) => value === false);
55-
};
56-
const handleRegister = async (event: React.MouseEvent<HTMLButtonElement>) => {
57-
event.preventDefault();
58-
if (validateForm()) {
59-
console.log("Registering user");
60-
61-
const data = await registerUser(username, password);
62-
if (data && data.username) {
63-
onContinue(data.recovery_codes);
64-
} else {
65-
setErrorMessage("Unexpected response from the server.");
66-
}
67-
}
68-
};
69-
70-
return (
71-
<Dialog open={open} onClose={onClose} aria-labelledby="form-dialog-title">
72-
<DialogContent>
73-
<Box
74-
component="form"
75-
noValidate
76-
sx={{
77-
display: "flex",
78-
flexDirection: "column",
79-
alignItems: "center",
80-
padding: "24px",
81-
}}
82-
>
83-
<Avatar sx={{ bgcolor: "secondary.main", marginBottom: 4 }}>
84-
<LockOutlinedIcon />
85-
</Avatar>
86-
<Typography variant="h5" align="center" sx={{ marginBottom: 4 }}>
87-
Register Admin User
88-
</Typography>
89-
<Box>
90-
{errorMessage && errorMessage != "" && (
91-
<Alert severity="error" sx={{ marginBottom: 2 }}>
92-
{errorMessage}
93-
</Alert>
94-
)}
95-
</Box>
96-
<TextField
97-
margin="normal"
98-
error={errors.username}
99-
helperText={errors.username ? "Please enter a username" : " "}
100-
required
101-
fullWidth
102-
id="register-username"
103-
label="Username"
104-
name="username"
105-
autoComplete="username"
106-
autoFocus
107-
value={username}
108-
onChange={(e) => setUsername(e.target.value)}
109-
/>
110-
<TextField
111-
margin="normal"
112-
error={errors.password}
113-
helperText={errors.password ? "Please enter a password" : " "}
114-
required
115-
fullWidth
116-
name="password"
117-
label="Password"
118-
type="password"
119-
id="register-password"
120-
autoComplete="new-password"
121-
value={password}
122-
onChange={(e) => setPassword(e.target.value)}
123-
/>
124-
<TextField
125-
required
126-
fullWidth
127-
name="confirm-password"
128-
label="Confirm Password"
129-
type="password"
130-
id="confirm-password"
131-
error={errors.confirmPasswordMatch}
132-
value={confirmPassword}
133-
helperText={errors.confirmPasswordMatch ? "Passwords do not match" : " "}
134-
onChange={(e) => setConfirmPassword(e.target.value)}
135-
/>
136-
<Box
137-
mt={1}
138-
width="100%"
139-
display="flex"
140-
flexDirection="column"
141-
alignItems="center"
142-
justifyContent="center"
143-
>
144-
<Button
145-
onClick={handleRegister}
146-
type="submit"
147-
fullWidth
148-
variant="contained"
149-
sx={{ maxWidth: "120px" }}
150-
>
151-
Register
152-
</Button>
153-
</Box>
154-
</Box>
155-
</DialogContent>
156-
</Dialog>
157-
);
158-
}
159-
9+
import React from "react";
10+
import { UserModal } from "@/app/user-management/components/UserCreateModal";
11+
import type { UserModalProps } from "@/app/user-management/components/UserCreateModal";
12+
const RegisterModal = (props: Omit<UserModalProps, "fields">) => (
13+
<UserModal
14+
{...props}
15+
fields={["username", "password", "confirmPassword"]}
16+
showCancel={false}
17+
/>
18+
);
16019
const AdminAlertModal = ({
16120
open,
16221
onClose,
@@ -183,76 +42,4 @@ const AdminAlertModal = ({
18342
);
18443
};
18544

186-
const ConfirmationModal = ({
187-
open,
188-
onClose,
189-
recoveryCodes,
190-
}: {
191-
open: boolean;
192-
onClose: () => void;
193-
recoveryCodes: string[];
194-
}) => {
195-
const [isClicked, setIsClicked] = useState(false);
196-
197-
const handleClose = () => {
198-
setIsClicked(false);
199-
onClose();
200-
};
201-
202-
const handleCopyToClipboard = async () => {
203-
try {
204-
await navigator.clipboard.writeText(recoveryCodes.join("\n"));
205-
} catch (err) {
206-
console.error("Failed to copy recovery codes: ", err);
207-
}
208-
};
209-
210-
return (
211-
<Dialog open={open} onClose={onClose}>
212-
<DialogTitle>Admin User Created</DialogTitle>
213-
<DialogContent>
214-
<DialogContentText>
215-
The admin user has been successfully registered. Please save the recovery
216-
codes below. You will not be able to see them again.
217-
</DialogContentText>
218-
<TextField
219-
fullWidth
220-
multiline
221-
margin="normal"
222-
value={recoveryCodes ? recoveryCodes.join("\n") : ""}
223-
InputProps={{
224-
readOnly: true,
225-
sx: {
226-
textAlign: "center",
227-
},
228-
}}
229-
inputProps={{
230-
style: { textAlign: "center" },
231-
}}
232-
/>
233-
234-
<Box display="flex" justifyContent="center" mt={2}>
235-
<Button
236-
variant="contained"
237-
onClick={() => {
238-
handleCopyToClipboard();
239-
setIsClicked(true);
240-
}}
241-
startIcon={isClicked ? <CheckIcon /> : <ContentCopyIcon />}
242-
style={{ paddingLeft: "20px", paddingRight: "20px" }}
243-
>
244-
{isClicked ? "Copied" : "Copy"}
245-
</Button>
246-
</Box>
247-
</DialogContent>
248-
249-
<DialogActions sx={{ marginBottom: 1, marginRight: 1 }}>
250-
<Button onClick={handleClose} color="primary" variant="contained" autoFocus>
251-
Back to Login
252-
</Button>
253-
</DialogActions>
254-
</Dialog>
255-
);
256-
};
257-
258-
export { AdminAlertModal, RegisterModal, ConfirmationModal };
45+
export { AdminAlertModal, RegisterModal };

admin_app/src/app/login/page.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import Typography from "@mui/material/Typography";
2020
import * as React from "react";
2121
import { useEffect } from "react";
2222
import { appColors, sizes } from "@/utils";
23-
import { apiCalls } from "@/utils/api";
2423
import {
25-
AdminAlertModal,
26-
ConfirmationModal,
27-
RegisterModal,
28-
} from "./components/RegisterModal";
24+
getRegisterOption,
25+
registerUser,
26+
UserBody,
27+
UserBodyPassword,
28+
} from "@/app/user-management/api";
29+
import { AdminAlertModal, RegisterModal } from "./components/RegisterModal";
30+
import { ConfirmationModal } from "@/app/user-management/components/ConfirmationModal";
2931
import { LoadingButton } from "@mui/lab";
3032

3133
const NEXT_PUBLIC_GOOGLE_LOGIN_CLIENT_ID: string =
@@ -62,7 +64,7 @@ const Login = () => {
6264

6365
useEffect(() => {
6466
const fetchRegisterPrompt = async () => {
65-
const data = await apiCalls.getRegisterOption();
67+
const data = await getRegisterOption();
6668
setShowAdminAlertModal(data.require_register);
6769
setIsLoading(false);
6870
};
@@ -100,12 +102,12 @@ const Login = () => {
100102
}
101103
}, [recoveryCodes]);
102104

103-
const handleAdminModalClose = (event: {}, reason: string) => {
105+
const handleAdminModalClose = (_?: {}, reason?: string) => {
104106
if (reason !== "backdropClick" && reason !== "escapeKeyDown") {
105107
setShowAdminAlertModal(false);
106108
}
107109
};
108-
const handleRegisterModalClose = (event: {}, reason: string) => {
110+
const handleRegisterModalClose = (_?: {}, reason?: string) => {
109111
if (reason !== "backdropClick" && reason !== "escapeKeyDown") {
110112
setShowRegisterModal(false);
111113
}
@@ -433,12 +435,16 @@ const Login = () => {
433435
open={showRegisterModal}
434436
onClose={handleRegisterModalClose}
435437
onContinue={handleRegisterModalContinue}
436-
registerUser={apiCalls.registerUser}
438+
registerUser={(user: UserBodyPassword | UserBody) => {
439+
const newUser = user as UserBodyPassword;
440+
return registerUser(newUser.username, newUser.password);
441+
}}
437442
/>
438443
<ConfirmationModal
439444
open={showConfirmationModal}
440445
onClose={handleCloseConfirmationModal}
441446
recoveryCodes={recoveryCodes}
447+
closeButtonText="Back to Login"
442448
/>
443449
</Grid>
444450
</Grid>

0 commit comments

Comments
 (0)