Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lickem22 user management #455

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 96 additions & 97 deletions .secrets.baseline

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion admin_app/src/app/integrations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useAuth } from "@/utils/auth";
import { KeyRenewConfirmationModal, NewKeyModal } from "./components/APIKeyModals";
import ConnectionsGrid from "./components/ConnectionsGrid";
import { LoadingButton } from "@mui/lab";
import { getUser } from "../user-management/api";

const IntegrationsPage = () => {
const [currAccessLevel, setCurrAccessLevel] = React.useState("readonly");
Expand Down Expand Up @@ -59,7 +60,7 @@ const KeyManagement = ({
const setApiKeyInfo = async () => {
setKeyInfoFetchIsLoading(true);
try {
const data = await apiCalls.getUser(token!);
lickem22 marked this conversation as resolved.
Show resolved Hide resolved
const data = await getUser(token!);
setCurrentKey(data.api_key_first_characters);
const formatted_api_update_date = format(
data.api_key_updated_datetime_utc,
Expand Down
235 changes: 11 additions & 224 deletions admin_app/src/app/login/components/RegisterModal.tsx
Original file line number Diff line number Diff line change
@@ -1,162 +1,21 @@
import CheckIcon from "@mui/icons-material/Check";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
import {
Alert,
Avatar,
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
TextField,
Typography,
} from "@mui/material";
import React, { useState } from "react";

interface User {
username: string;
recovery_codes: string[];
}
interface RegisterModalProps {
open: boolean;
onClose: (event: {}, reason: string) => void;
onContinue: (recoveryCodes: string[]) => void;
registerUser: (username: string, password: string) => Promise<User>;
}

function RegisterModal({
open,
onClose,
onContinue,
registerUser,
}: RegisterModalProps) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [errors, setErrors] = React.useState({
username: false,
password: false,
confirmPassword: false,
confirmPasswordMatch: false,
});
const validateForm = () => {
const newErrors = {
username: username === "",
password: password === "",
confirmPassword: confirmPassword === "",
confirmPasswordMatch: password !== confirmPassword,
};
setErrors(newErrors);
return Object.values(newErrors).every((value) => value === false);
};
const handleRegister = async (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
if (validateForm()) {
console.log("Registering user");

const data = await registerUser(username, password);
if (data && data.username) {
onContinue(data.recovery_codes);
} else {
setErrorMessage("Unexpected response from the server.");
}
}
};

return (
<Dialog open={open} onClose={onClose} aria-labelledby="form-dialog-title">
<DialogContent>
<Box
component="form"
noValidate
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: "24px",
}}
>
<Avatar sx={{ bgcolor: "secondary.main", marginBottom: 4 }}>
<LockOutlinedIcon />
</Avatar>
<Typography variant="h5" align="center" sx={{ marginBottom: 4 }}>
Register Admin User
</Typography>
<Box>
{errorMessage && errorMessage != "" && (
<Alert severity="error" sx={{ marginBottom: 2 }}>
{errorMessage}
</Alert>
)}
</Box>
<TextField
margin="normal"
error={errors.username}
helperText={errors.username ? "Please enter a username" : " "}
required
fullWidth
id="register-username"
label="Username"
name="username"
autoComplete="username"
autoFocus
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<TextField
margin="normal"
error={errors.password}
helperText={errors.password ? "Please enter a password" : " "}
required
fullWidth
name="password"
label="Password"
type="password"
id="register-password"
autoComplete="new-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<TextField
required
fullWidth
name="confirm-password"
label="Confirm Password"
type="password"
id="confirm-password"
error={errors.confirmPasswordMatch}
value={confirmPassword}
helperText={errors.confirmPasswordMatch ? "Passwords do not match" : " "}
onChange={(e) => setConfirmPassword(e.target.value)}
/>
<Box
mt={1}
width="100%"
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
>
<Button
onClick={handleRegister}
type="submit"
fullWidth
variant="contained"
sx={{ maxWidth: "120px" }}
>
Register
</Button>
</Box>
</Box>
</DialogContent>
</Dialog>
);
}

import React from "react";
import { UserModal } from "@/app/user-management/components/UserCreateModal";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should UserModal be under app/components since it seems to be used in multiple places?

The coupling between low-level components (app/login/components/RegisterModal -> user-management/components/UserCreateModal) seems like a bad idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's one of things that leads to code bases where if you change one thing, you have to now change a bunch of unrelated components

import type { UserModalProps } from "@/app/user-management/components/UserCreateModal";
const RegisterModal = (props: Omit<UserModalProps, "fields">) => (
<UserModal
{...props}
fields={["username", "password", "confirmPassword"]}
showCancel={false}
/>
);
const AdminAlertModal = ({
open,
onClose,
Expand All @@ -183,76 +42,4 @@ const AdminAlertModal = ({
);
};

const ConfirmationModal = ({
open,
onClose,
recoveryCodes,
}: {
open: boolean;
onClose: () => void;
recoveryCodes: string[];
}) => {
const [isClicked, setIsClicked] = useState(false);

const handleClose = () => {
setIsClicked(false);
onClose();
};

const handleCopyToClipboard = async () => {
try {
await navigator.clipboard.writeText(recoveryCodes.join("\n"));
} catch (err) {
console.error("Failed to copy recovery codes: ", err);
}
};

return (
<Dialog open={open} onClose={onClose}>
<DialogTitle>Admin User Created</DialogTitle>
<DialogContent>
<DialogContentText>
The admin user has been successfully registered. Please save the recovery
codes below. You will not be able to see them again.
</DialogContentText>
<TextField
fullWidth
multiline
margin="normal"
value={recoveryCodes ? recoveryCodes.join("\n") : ""}
InputProps={{
readOnly: true,
sx: {
textAlign: "center",
},
}}
inputProps={{
style: { textAlign: "center" },
}}
/>

<Box display="flex" justifyContent="center" mt={2}>
<Button
variant="contained"
onClick={() => {
handleCopyToClipboard();
setIsClicked(true);
}}
startIcon={isClicked ? <CheckIcon /> : <ContentCopyIcon />}
style={{ paddingLeft: "20px", paddingRight: "20px" }}
>
{isClicked ? "Copied" : "Copy"}
</Button>
</Box>
</DialogContent>

<DialogActions sx={{ marginBottom: 1, marginRight: 1 }}>
<Button onClick={handleClose} color="primary" variant="contained" autoFocus>
Back to Login
</Button>
</DialogActions>
</Dialog>
);
};

export { AdminAlertModal, RegisterModal, ConfirmationModal };
export { AdminAlertModal, RegisterModal };
24 changes: 15 additions & 9 deletions admin_app/src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ import Typography from "@mui/material/Typography";
import * as React from "react";
import { useEffect } from "react";
import { appColors, sizes } from "@/utils";
import { apiCalls } from "@/utils/api";
import {
AdminAlertModal,
ConfirmationModal,
RegisterModal,
} from "./components/RegisterModal";
getRegisterOption,
registerUser,
UserBody,
UserBodyPassword,
} from "@/app/user-management/api";
import { AdminAlertModal, RegisterModal } from "./components/RegisterModal";
import { ConfirmationModal } from "@/app/user-management/components/ConfirmationModal";
import { LoadingButton } from "@mui/lab";

const NEXT_PUBLIC_GOOGLE_LOGIN_CLIENT_ID: string =
Expand Down Expand Up @@ -62,7 +64,7 @@ const Login = () => {

useEffect(() => {
const fetchRegisterPrompt = async () => {
const data = await apiCalls.getRegisterOption();
const data = await getRegisterOption();
setShowAdminAlertModal(data.require_register);
setIsLoading(false);
};
Expand Down Expand Up @@ -100,12 +102,12 @@ const Login = () => {
}
}, [recoveryCodes]);

const handleAdminModalClose = (event: {}, reason: string) => {
const handleAdminModalClose = (event?: {}, reason?: string) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not using event, let's make it _.
Also, why is reason optional? I don't think it should be?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see why you are making it optional. It's to meet the type requirement for onClose on UserModalProps.
Maybe that should be a different signature. I'll comment there as well.

if (reason !== "backdropClick" && reason !== "escapeKeyDown") {
setShowAdminAlertModal(false);
}
};
const handleRegisterModalClose = (event: {}, reason: string) => {
const handleRegisterModalClose = (event?: {}, reason?: string) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

if (reason !== "backdropClick" && reason !== "escapeKeyDown") {
setShowRegisterModal(false);
}
Expand Down Expand Up @@ -433,12 +435,16 @@ const Login = () => {
open={showRegisterModal}
onClose={handleRegisterModalClose}
onContinue={handleRegisterModalContinue}
registerUser={apiCalls.registerUser}
registerUser={(user: UserBodyPassword | UserBody) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is UserBody type needed for user here? Is it for google login user and the password gets set as blank?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a "Parent" modal UserModal that can be done to register the first user, register all users and edit users. Since it can do both registering and editing it needs to support UserBodyPassword and UserBody, If I do :
(user: UserBodyPassword) I am going to get a syntax error.

const newUser = user as UserBodyPassword;
return registerUser(newUser.username, newUser.password);
}}
/>
<ConfirmationModal
open={showConfirmationModal}
onClose={handleCloseConfirmationModal}
recoveryCodes={recoveryCodes}
closeButtonText="Back to Login"
/>
</Grid>
</Grid>
Expand Down
Loading