refactor: totp

feat-otp-email-verify
James Elliott 2023-03-06 06:33:02 +11:00
parent bf5272dc61
commit a1351b1473
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
7 changed files with 235 additions and 134 deletions

View File

@ -2,7 +2,8 @@
"Actions": "Actions", "Actions": "Actions",
"Add": "Add", "Add": "Add",
"Add Credential": "Add Credential", "Add Credential": "Add Credential",
"Added": "Added {{when, datetime}}", "Added": "Added",
"Added when": "Added {{when, datetime}}",
"Are you sure you want to remove the WebAuthn credential from from your account": "Are you sure you want to remove the WebAuthn credential {{description}} from your account?", "Are you sure you want to remove the WebAuthn credential from from your account": "Are you sure you want to remove the WebAuthn credential {{description}} from your account?",
"Attestation Type": "Attestation Type", "Attestation Type": "Attestation Type",
"Authenticator GUID": "Authenticator GUID", "Authenticator GUID": "Authenticator GUID",
@ -22,11 +23,12 @@
"Enter a description for this credential": "Enter a description for this credential", "Enter a description for this credential": "Enter a description for this credential",
"Extended WebAuthn credential information for security key": "Extended WebAuthn credential information for security key {{description}}", "Extended WebAuthn credential information for security key": "Extended WebAuthn credential information for security key {{description}}",
"Identifier": "Identifier", "Identifier": "Identifier",
"Last Used": "Last Used {{when, datetime}}", "Last Used": "Last Used",
"Last Used when": "Last Used {{when, datetime}}",
"Manage your security keys": "Manage your security keys", "Manage your security keys": "Manage your security keys",
"Name": "Name", "Name": "Name",
"No": "No", "No": "No",
"No Registered WebAuthn Credentials": "No Registered WebAuthn Credentials", "No WebAuthn Credentials have been registered. If you'd like to register one click add.": "No WebAuthn Credentials have been registered. If you'd like to register one click add.",
"Overview": "Overview", "Overview": "Overview",
"Provide the details for the new security key": "Provide the details for the new security key", "Provide the details for the new security key": "Provide the details for the new security key",
"Register WebAuthn Credential": "Register WebAuthn Credential", "Register WebAuthn Credential": "Register WebAuthn Credential",

View File

@ -1,6 +0,0 @@
import { useRemoteCall } from "@hooks/RemoteCall";
import { getUserWebauthnDevices } from "@services/UserWebauthnDevices";
export function useUserWebauthnDevices() {
return useRemoteCall(getUserWebauthnDevices, []);
}

View File

@ -1,21 +1,22 @@
import React, { ReactNode, useEffect } from "react"; import React, { ReactNode, SyntheticEvent, useCallback, useEffect, useState } from "react";
import { Dashboard } from "@mui/icons-material"; import { Close, Dashboard } from "@mui/icons-material";
import MenuIcon from "@mui/icons-material/Menu";
import SystemSecurityUpdateGoodIcon from "@mui/icons-material/SystemSecurityUpdateGood"; import SystemSecurityUpdateGoodIcon from "@mui/icons-material/SystemSecurityUpdateGood";
import { import {
AppBar, AppBar,
Box, Box,
Button, Divider,
Drawer,
Grid,
List, List,
ListItem, ListItem,
ListItemButton, ListItemButton,
ListItemIcon, ListItemIcon,
ListItemText, ListItemText,
SwipeableDrawer,
Toolbar, Toolbar,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import IconButton from "@mui/material/IconButton";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { IndexRoute, SettingsRoute, SettingsTwoFactorAuthenticationSubRoute } from "@constants/Routes"; import { IndexRoute, SettingsRoute, SettingsTwoFactorAuthenticationSubRoute } from "@constants/Routes";
@ -33,8 +34,7 @@ const defaultDrawerWidth = 240;
const SettingsLayout = function (props: Props) { const SettingsLayout = function (props: Props) {
const { t: translate } = useTranslation("settings"); const { t: translate } = useTranslation("settings");
const [drawerOpen, setDrawerOpen] = useState(false);
const navigate = useRouterNavigate();
useEffect(() => { useEffect(() => {
if (props.title) { if (props.title) {
@ -54,72 +54,179 @@ const SettingsLayout = function (props: Props) {
const drawerWidth = props.drawerWidth === undefined ? defaultDrawerWidth : props.drawerWidth; const drawerWidth = props.drawerWidth === undefined ? defaultDrawerWidth : props.drawerWidth;
const handleToggleDrawer = (event: SyntheticEvent) => {
if (
event.nativeEvent instanceof KeyboardEvent &&
event.nativeEvent.type === "keydown" &&
(event.nativeEvent.key === "Tab" || event.nativeEvent.key === "Shift")
) {
return;
}
setDrawerOpen((state) => !state);
};
const container = window !== undefined ? () => window.document.body : undefined;
const drawer = (
<Box onClick={handleToggleDrawer} sx={{ textAlign: "center" }}>
<Typography variant="h6" sx={{ my: 2 }}>
{translate("Settings")}
</Typography>
<Divider />
<List>
{navItems.map((item) => (
<DrawerNavItem key={item.keyname} text={item.text} pathname={item.pathname} icon={item.icon} />
))}
</List>
</Box>
);
return ( return (
<Box sx={{ display: "flex" }}> <Box sx={{ display: "flex" }}>
<AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}> <AppBar component={"nav"}>
<Toolbar variant="dense"> <Toolbar>
<Typography style={{ flexGrow: 1 }}>Authelia {translate("Settings")}</Typography> <IconButton
<Button edge="start"
variant="contained" color="inherit"
color="success" aria-label="open drawer"
onClick={() => { onClick={handleToggleDrawer}
navigate(IndexRoute); sx={{ mr: 2 }}
}}
> >
{translate("Close")} <MenuIcon />
</Button> </IconButton>
<Typography
variant="h6"
component={"div"}
sx={{ flexGrow: 1, display: { xs: "none", sm: drawerOpen ? "none" : "block" } }}
>
{translate("Settings")}
</Typography>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Drawer <Box component={"nav"}>
variant="permanent" <SwipeableDrawer
sx={{ container={container}
width: drawerWidth, anchor={"left"}
flexShrink: 0, open={drawerOpen}
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: "border-box" }, onOpen={handleToggleDrawer}
}} onClose={handleToggleDrawer}
> ModalProps={{
<Toolbar variant="dense" /> keepMounted: true,
<Box sx={{ overflow: "auto" }}> }}
<List> sx={{
<SettingsMenuItem pathname={SettingsRoute} text={translate("Overview")} icon={<Dashboard />} /> display: { xs: "block" },
<SettingsMenuItem "& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth },
pathname={`${SettingsRoute}${SettingsTwoFactorAuthenticationSubRoute}`} }}
text={translate("Two-Factor Authentication")} >
icon={<SystemSecurityUpdateGoodIcon />} {drawer}
/> </SwipeableDrawer>
</List> </Box>
</Box> <Box component="main" sx={{ flexGrow: 1, p: 3 }}>
</Drawer> <Toolbar />
<Grid container id={props.id} spacing={0}> {props.children}
<Grid item xs={12}> </Box>
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<Toolbar variant="dense" />
{props.children}
</Box>
</Grid>
</Grid>
</Box> </Box>
); );
}; };
export default SettingsLayout; export default SettingsLayout;
interface SettingsMenuItemProps { interface NavItem {
pathname: string; keyname?: string;
text: string; text: string;
icon: ReactNode; pathname: string;
icon?: ReactNode;
} }
const SettingsMenuItem = function (props: SettingsMenuItemProps) { const navItems: NavItem[] = [
{ keyname: "overview", text: "Overview", pathname: SettingsRoute, icon: <Dashboard color={"primary"} /> },
{
keyname: "twofactor",
text: "Two-Factor Authentication",
pathname: `${SettingsRoute}${SettingsTwoFactorAuthenticationSubRoute}`,
icon: <SystemSecurityUpdateGoodIcon color={"primary"} />,
},
{ keyname: "close", text: "Close", pathname: IndexRoute, icon: <Close color={"error"} /> },
];
const DrawerNavItem = function (props: NavItem) {
const selected = window.location.pathname === props.pathname || window.location.pathname === props.pathname + "/"; const selected = window.location.pathname === props.pathname || window.location.pathname === props.pathname + "/";
const navigate = useRouterNavigate(); const navigate = useRouterNavigate();
const handleOnClick = useCallback(() => {
if (selected) {
return;
}
navigate(props.pathname);
}, [navigate, props, selected]);
return ( return (
<ListItem disablePadding onClick={!selected ? () => navigate(props.pathname) : undefined}> <ListItem disablePadding onClick={handleOnClick}>
<ListItemButton selected={selected}> <ListItemButton selected={selected}>
<ListItemIcon>{props.icon}</ListItemIcon> {props.icon ? <ListItemIcon>{props.icon}</ListItemIcon> : null}
<ListItemText primary={props.text} /> <ListItemText primary={props.text} />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
); );
}; };
/*
interface SettingsMenuProps {
isXL: boolean;
handleClickMenuItem: () => void;
handleToggleDrawer: (open: boolean) => (event: SyntheticEvent) => void;
}
const SettingsMenu = function (props: SettingsMenuProps) {
const { t: translate } = useTranslation("settings");
const navigate = useRouterNavigate();
return (
<Box
sx={{
p: 2,
height: 1,
}}
>
{props.isXL ? null : (
<Fragment>
<IconButton sx={{ mb: 2 }} onClick={props.handleToggleDrawer(false)}>
<Close />
</IconButton>
<Divider sx={{ mb: 2 }} />
</Fragment>
)}
<List sx={{ mb: 2 }}>
<SettingsMenuItem
pathname={SettingsRoute}
text={translate("Overview")}
icon={<Dashboard color={"primary"} />}
onClick={props.handleClickMenuItem}
/>
<SettingsMenuItem
pathname={`${SettingsRoute}${SettingsTwoFactorAuthenticationSubRoute}`}
text={translate("Two-Factor Authentication")}
icon={<SystemSecurityUpdateGoodIcon color={"primary"} />}
onClick={props.handleClickMenuItem}
/>
<ListItem
disablePadding
onClick={() => {
navigate(IndexRoute);
}}
>
<ListItemButton>
<ListItemIcon>
<Close color={"error"} />
</ListItemIcon>
<ListItemText primary={"Close"} />
</ListItemButton>
</ListItem>
</List>
</Box>
);
};
*/

View File

@ -11,7 +11,6 @@ import { deleteUserTOTPConfiguration } from "@services/UserInfoTOTPConfiguration
import DeleteDialog from "@views/Settings/TwoFactorAuthentication/DeleteDialog"; import DeleteDialog from "@views/Settings/TwoFactorAuthentication/DeleteDialog";
interface Props { interface Props {
index: number;
config: UserInfoTOTPConfiguration; config: UserInfoTOTPConfiguration;
handleRefresh: () => void; handleRefresh: () => void;
} }
@ -69,14 +68,14 @@ export default function TOTPDevice(props: Props) {
"Are you sure you want to remove the Time-based One Time Password from from your account", "Are you sure you want to remove the Time-based One Time Password from from your account",
)} )}
/> />
<Stack direction="row" spacing={1} alignItems="center"> <Stack direction={"row"} spacing={1} alignItems={"center"}>
<QrCode2 fontSize="large" /> <QrCode2 fontSize="large" />
<Stack spacing={0} sx={{ minWidth: 400 }}> <Stack spacing={0} sx={{ minWidth: 400 }}>
<Box> <Box>
<Typography display="inline" sx={{ fontWeight: "bold" }}> <Typography display={"inline"} sx={{ fontWeight: "bold" }}>
{props.config.issuer} {props.config.issuer}
</Typography> </Typography>
<Typography display="inline" variant="body2"> <Typography display={"inline"} variant={"body2"}>
{" (" + {" (" +
translate("{{algorithm}}, {{digits}} digits, {{seconds}} seconds", { translate("{{algorithm}}, {{digits}} digits, {{seconds}} seconds", {
algorithm: toAlgorithmString(props.config.algorithm), algorithm: toAlgorithmString(props.config.algorithm),
@ -87,7 +86,7 @@ export default function TOTPDevice(props: Props) {
</Typography> </Typography>
</Box> </Box>
<Typography variant={"caption"}> <Typography variant={"caption"}>
{translate("Added", { {translate("Added when", {
when: props.config.created_at, when: props.config.created_at,
formatParams: { formatParams: {
when: { when: {
@ -103,7 +102,7 @@ export default function TOTPDevice(props: Props) {
<Typography variant={"caption"}> <Typography variant={"caption"}>
{props.config.last_used_at === undefined {props.config.last_used_at === undefined
? translate("Never used") ? translate("Never used")
: translate("Last Used", { : translate("Last Used when", {
when: props.config.last_used_at, when: props.config.last_used_at,
formatParams: { formatParams: {
when: { when: {
@ -117,7 +116,6 @@ export default function TOTPDevice(props: Props) {
})} })}
</Typography> </Typography>
</Stack> </Stack>
<Tooltip title={translate("Remove the Time-based One Time Password configuration")}> <Tooltip title={translate("Remove the Time-based One Time Password configuration")}>
<Button <Button
variant={"outlined"} variant={"outlined"}

View File

@ -1,6 +1,7 @@
import React, { Fragment, useState } from "react"; import React, { Fragment, useState } from "react";
import { Button, Grid, Paper, Stack, Tooltip, Typography } from "@mui/material"; import { Button, Paper, Stack, Tooltip, Typography } from "@mui/material";
import Grid from "@mui/material/Unstable_Grid2";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UserInfoTOTPConfiguration } from "@models/TOTPConfiguration"; import { UserInfoTOTPConfiguration } from "@models/TOTPConfiguration";
@ -26,14 +27,14 @@ export default function TOTPPanel(props: Props) {
props.handleRefreshState(); props.handleRefreshState();
}} }}
/> />
<Paper variant="outlined" sx={{ p: 3 }}> <Paper variant={"outlined"}>
<Grid container spacing={1}> <Grid container spacing={2} padding={2}>
<Grid item xs={12}> <Grid xs={12} lg={8}>
<Typography variant="h5">{translate("One Time Password")}</Typography> <Typography variant={"h5"}>{translate("One Time Password")}</Typography>
</Grid> </Grid>
{props.config === undefined || props.config === null ? ( {props.config === undefined || props.config === null ? (
<Fragment> <Fragment>
<Grid item xs={12}> <Grid xs={2}>
<Tooltip <Tooltip
title={translate("Click to add a Time-based One Time Password to your account")} title={translate("Click to add a Time-based One Time Password to your account")}
> >
@ -48,7 +49,7 @@ export default function TOTPPanel(props: Props) {
</Button> </Button>
</Tooltip> </Tooltip>
</Grid> </Grid>
<Grid item xs={12}> <Grid xs={12}>
<Typography variant={"subtitle2"}> <Typography variant={"subtitle2"}>
{translate( {translate(
"The One Time Password has not been registered. If you'd like to register it click add.", "The One Time Password has not been registered. If you'd like to register it click add.",
@ -57,9 +58,9 @@ export default function TOTPPanel(props: Props) {
</Grid> </Grid>
</Fragment> </Fragment>
) : ( ) : (
<Grid item xs={12}> <Grid xs={12}>
<Stack spacing={2}> <Stack spacing={3}>
<TOTPDevice index={0} config={props.config} handleRefresh={props.handleRefreshState} /> <TOTPDevice config={props.config} handleRefresh={props.handleRefreshState} />
</Stack> </Stack>
</Grid> </Grid>
)} )}

View File

@ -2,6 +2,7 @@ import React, { Fragment, useCallback, useEffect, useState } from "react";
import { IconDefinition, faCopy, faKey, faTimesCircle } from "@fortawesome/free-solid-svg-icons"; import { IconDefinition, faCopy, faKey, faTimesCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Visibility } from "@mui/icons-material";
import { import {
Box, Box,
Button, Button,
@ -9,11 +10,11 @@ import {
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogContentText,
DialogTitle, DialogTitle,
FormControl, FormControl,
FormControlLabel, FormControlLabel,
FormLabel, FormLabel,
Grid,
IconButton, IconButton,
Link, Link,
Radio, Radio,
@ -26,6 +27,7 @@ import {
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import { red } from "@mui/material/colors"; import { red } from "@mui/material/colors";
import Grid from "@mui/material/Unstable_Grid2";
import makeStyles from "@mui/styles/makeStyles"; import makeStyles from "@mui/styles/makeStyles";
import classnames from "classnames"; import classnames from "classnames";
import { QRCodeSVG } from "qrcode.react"; import { QRCodeSVG } from "qrcode.react";
@ -69,6 +71,7 @@ export default function TOTPRegisterDialogController(props: Props) {
const [hasErrored, setHasErrored] = useState(false); const [hasErrored, setHasErrored] = useState(false);
const [dialValue, setDialValue] = useState(""); const [dialValue, setDialValue] = useState("");
const [dialState, setDialState] = useState(State.Idle); const [dialState, setDialState] = useState(State.Idle);
const [totpSecretURLHidden, setTOTPSecretURLHidden] = useState(true);
const resetStates = () => { const resetStates = () => {
setOptions(null); setOptions(null);
@ -85,6 +88,7 @@ export default function TOTPRegisterDialogController(props: Props) {
setActiveStep(0); setActiveStep(0);
setDialValue(""); setDialValue("");
setDialState(State.Idle); setDialState(State.Idle);
setTOTPSecretURLHidden(true);
}; };
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
@ -112,7 +116,7 @@ export default function TOTPRegisterDialogController(props: Props) {
}; };
useEffect(() => { useEffect(() => {
if (!props.open || activeStep !== 0) { if (!props.open || activeStep !== 0 || options !== null) {
return; return;
} }
@ -126,13 +130,14 @@ export default function TOTPRegisterDialogController(props: Props) {
setOptionPeriod(opts.period); setOptionPeriod(opts.period);
setOptionPeriods(opts.periods.map((period) => period.toString())); setOptionPeriods(opts.periods.map((period) => period.toString()));
})(); })();
}, [props.open, activeStep]); }, [props.open, activeStep, options]);
const handleSetStepPrevious = useCallback(() => { const handleSetStepPrevious = useCallback(() => {
if (activeStep === 0) { if (activeStep === 0) {
return; return;
} }
setShowAdvanced(false);
setActiveStep((prevState) => (prevState -= 1)); setActiveStep((prevState) => (prevState -= 1));
}, [activeStep]); }, [activeStep]);
@ -141,6 +146,7 @@ export default function TOTPRegisterDialogController(props: Props) {
return; return;
} }
setShowAdvanced(false);
setActiveStep((prevState) => (prevState += 1)); setActiveStep((prevState) => (prevState += 1));
}, [activeStep]); }, [activeStep]);
@ -213,7 +219,6 @@ export default function TOTPRegisterDialogController(props: Props) {
function SecretButton(text: string | undefined, action: string, icon: IconDefinition) { function SecretButton(text: string | undefined, action: string, icon: IconDefinition) {
return ( return (
<IconButton <IconButton
className={styles.secretButtons}
color="primary" color="primary"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(`${text}`); navigator.clipboard.writeText(`${text}`);
@ -232,21 +237,20 @@ export default function TOTPRegisterDialogController(props: Props) {
return ( return (
<Fragment> <Fragment>
{options === null ? ( {options === null ? (
<Grid item xs={12}> <Grid xs={12}>
<Typography>Loading...</Typography> <Typography>Loading...</Typography>
</Grid> </Grid>
) : ( ) : (
<Fragment> <Grid container>
<Grid item xs={12}> <Grid xs={12}>
<Typography>{translate("To begin select next")}</Typography> <Typography>{translate("To begin select next")}</Typography>
</Grid> </Grid>
<Grid item xs={12} hidden={hideAdvanced}> <Grid xs={12} hidden={hideAdvanced}>
<Button variant={"outlined"} color={"warning"} onClick={toggleAdvanced}> <Button variant={"outlined"} color={"warning"} onClick={toggleAdvanced}>
{showAdvanced ? translate("Hide Advanced") : translate("Show Advanced")} {showAdvanced ? translate("Hide Advanced") : translate("Show Advanced")}
</Button> </Button>
</Grid> </Grid>
<Grid <Grid
item
xs={12} xs={12}
hidden={hideAdvanced || !showAdvanced} hidden={hideAdvanced || !showAdvanced}
justifyContent={"center"} justifyContent={"center"}
@ -330,15 +334,15 @@ export default function TOTPRegisterDialogController(props: Props) {
</RadioGroup> </RadioGroup>
</FormControl> </FormControl>
</Grid> </Grid>
</Fragment> </Grid>
)} )}
</Fragment> </Fragment>
); );
case 1: case 1:
return ( return (
<Fragment> <Fragment>
<Grid item xs={12}> <Grid xs={12}>
<Box className={styles.googleAuthenticator}> <Box>
<Typography className={styles.googleAuthenticatorText}> <Typography className={styles.googleAuthenticatorText}>
{translate("Need Google Authenticator?")} {translate("Need Google Authenticator?")}
</Typography> </Typography>
@ -351,10 +355,10 @@ export default function TOTPRegisterDialogController(props: Props) {
/> />
</Box> </Box>
</Grid> </Grid>
<Grid item xs={12}> <Grid xs={12}>
<Box className={classnames(qrcodeFuzzyStyle, styles.qrcodeContainer)}> <Box className={classnames(qrcodeFuzzyStyle, styles.qrcodeContainer)}>
<Link href={totpSecretURL} underline="hover"> <Link href={totpSecretURL} underline="hover">
<QRCodeSVG value={totpSecretURL} className={styles.qrcode} size={256} /> <QRCodeSVG value={totpSecretURL} className={styles.qrcode} size={128} />
{!hasErrored && totpIsLoading ? ( {!hasErrored && totpIsLoading ? (
<CircularProgress className={styles.loader} size={128} /> <CircularProgress className={styles.loader} size={128} />
) : null} ) : null}
@ -364,22 +368,20 @@ export default function TOTPRegisterDialogController(props: Props) {
</Link> </Link>
</Box> </Box>
</Grid> </Grid>
<Grid item xs={12}> <Grid xs={12}>
<Grid container spacing={2} justifyContent={"center"}> <Grid container spacing={2} justifyContent={"center"}>
<Grid item xs={12}> <Grid xs={2}>
{totpSecretURL !== "empty" ? ( <IconButton
<TextField color="primary"
id="secret-url" onClick={() => {
label={translate("Secret")} setTOTPSecretURLHidden((value) => !value);
className={styles.secret} }}
value={totpSecretURL} size="large"
InputProps={{ >
readOnly: true, <Visibility />
}} </IconButton>
/>
) : null}
</Grid> </Grid>
<Grid item xs={2}> <Grid xs={2}>
{totpSecretBase32 {totpSecretBase32
? SecretButton( ? SecretButton(
totpSecretBase32, totpSecretBase32,
@ -388,18 +390,31 @@ export default function TOTPRegisterDialogController(props: Props) {
) )
: null} : null}
</Grid> </Grid>
<Grid item xs={2}> <Grid xs={2}>
{totpSecretURL !== "empty" {totpSecretURL !== ""
? SecretButton(totpSecretURL, translate("OTP URL copied to clipboard"), faCopy) ? SecretButton(totpSecretURL, translate("OTP URL copied to clipboard"), faCopy)
: null} : null}
</Grid> </Grid>
<Grid xs={12}>
<TextField
id="secret-url"
label={translate("Secret")}
className={styles.secret}
value={totpSecretURL}
multiline={true}
hidden={totpSecretURLHidden || totpSecretURL === ""}
InputProps={{
readOnly: true,
}}
/>
</Grid>
</Grid> </Grid>
</Grid> </Grid>
</Fragment> </Fragment>
); );
case 2: case 2:
return ( return (
<Grid item xs={12}> <Grid xs={12}>
<OTPDial <OTPDial
passcode={dialValue} passcode={dialValue}
state={dialState} state={dialState}
@ -416,8 +431,11 @@ export default function TOTPRegisterDialogController(props: Props) {
<Dialog open={props.open} onClose={handleOnClose} maxWidth={"xs"} fullWidth={true}> <Dialog open={props.open} onClose={handleOnClose} maxWidth={"xs"} fullWidth={true}>
<DialogTitle>{translate("Register One Time Password (TOTP)")}</DialogTitle> <DialogTitle>{translate("Register One Time Password (TOTP)")}</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText sx={{ mb: 3 }}>
{translate("This dialog allows registration of the One-Time Password.")}
</DialogContentText>
<Grid container spacing={0} alignItems={"center"} justifyContent={"center"} textAlign={"center"}> <Grid container spacing={0} alignItems={"center"} justifyContent={"center"} textAlign={"center"}>
<Grid item xs={12}> <Grid xs={12}>
<Stepper activeStep={activeStep}> <Stepper activeStep={activeStep}>
{steps.map((label, index) => { {steps.map((label, index) => {
const stepProps: { completed?: boolean } = {}; const stepProps: { completed?: boolean } = {};
@ -432,7 +450,7 @@ export default function TOTPRegisterDialogController(props: Props) {
})} })}
</Stepper> </Stepper>
</Grid> </Grid>
<Grid item xs={12}> <Grid xs={12}>
<Grid container spacing={2} paddingY={3} justifyContent={"center"}> <Grid container spacing={2} paddingY={3} justifyContent={"center"}>
{renderStep(activeStep)} {renderStep(activeStep)}
</Grid> </Grid>
@ -440,23 +458,13 @@ export default function TOTPRegisterDialogController(props: Props) {
</Grid> </Grid>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button <Button color={"primary"} onClick={handleSetStepPrevious} disabled={activeStep === 0}>
variant={"outlined"}
color={"primary"}
onClick={handleSetStepPrevious}
disabled={activeStep === 0}
>
{translate("Previous")} {translate("Previous")}
</Button> </Button>
<Button variant={"outlined"} color={"primary"} onClick={handleClose}> <Button color={"error"} onClick={handleClose}>
{translate("Cancel")} {translate("Cancel")}
</Button> </Button>
<Button <Button color={"primary"} onClick={handleSetStepNext} disabled={activeStep === steps.length - 1}>
variant={"outlined"}
color={"primary"}
onClick={handleSetStepNext}
disabled={activeStep === steps.length - 1}
>
{translate("Next")} {translate("Next")}
</Button> </Button>
</DialogActions> </DialogActions>
@ -465,10 +473,6 @@ export default function TOTPRegisterDialogController(props: Props) {
} }
const useStyles = makeStyles((theme: Theme) => ({ const useStyles = makeStyles((theme: Theme) => ({
root: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
},
qrcode: { qrcode: {
marginTop: theme.spacing(2), marginTop: theme.spacing(2),
marginBottom: theme.spacing(2), marginBottom: theme.spacing(2),
@ -483,15 +487,10 @@ const useStyles = makeStyles((theme: Theme) => ({
marginBottom: theme.spacing(1), marginBottom: theme.spacing(1),
width: "256px", width: "256px",
}, },
googleAuthenticator: {},
googleAuthenticatorText: { googleAuthenticatorText: {
fontSize: theme.typography.fontSize * 0.8, fontSize: theme.typography.fontSize * 0.8,
}, },
googleAuthenticatorBadges: {}, googleAuthenticatorBadges: {},
secretButtons: {},
doneButton: {
width: "256px",
},
qrcodeContainer: { qrcodeContainer: {
position: "relative", position: "relative",
display: "inline-block", display: "inline-block",

View File

@ -4,7 +4,7 @@ import Grid from "@mui/material/Unstable_Grid2";
import { useNotifications } from "@hooks/NotificationsContext"; import { useNotifications } from "@hooks/NotificationsContext";
import { useUserInfoPOST } from "@hooks/UserInfo"; import { useUserInfoPOST } from "@hooks/UserInfo";
import { useUserInfoTOTPConfiguration, useUserInfoTOTPConfigurationOptional } from "@hooks/UserInfoTOTPConfiguration"; import { useUserInfoTOTPConfigurationOptional } from "@hooks/UserInfoTOTPConfiguration";
import { useUserWebAuthnDevices } from "@hooks/WebAuthnDevices"; import { useUserWebAuthnDevices } from "@hooks/WebAuthnDevices";
import TOTPPanel from "@views/Settings/TwoFactorAuthentication/TOTPPanel"; import TOTPPanel from "@views/Settings/TwoFactorAuthentication/TOTPPanel";
import WebAuthnDevicesPanel from "@views/Settings/TwoFactorAuthentication/WebAuthnDevicesPanel"; import WebAuthnDevicesPanel from "@views/Settings/TwoFactorAuthentication/WebAuthnDevicesPanel";