refactor: totp
parent
b03c0b7ace
commit
d7d280dd54
|
@ -2,7 +2,8 @@
|
|||
"Actions": "Actions",
|
||||
"Add": "Add",
|
||||
"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?",
|
||||
"Attestation Type": "Attestation Type",
|
||||
"Authenticator GUID": "Authenticator GUID",
|
||||
|
@ -22,11 +23,12 @@
|
|||
"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}}",
|
||||
"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",
|
||||
"Name": "Name",
|
||||
"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",
|
||||
"Provide the details for the new security key": "Provide the details for the new security key",
|
||||
"Register WebAuthn Credential": "Register WebAuthn Credential",
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
import { useRemoteCall } from "@hooks/RemoteCall";
|
||||
import { getUserWebauthnDevices } from "@services/UserWebauthnDevices";
|
||||
|
||||
export function useUserWebauthnDevices() {
|
||||
return useRemoteCall(getUserWebauthnDevices, []);
|
||||
}
|
|
@ -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 {
|
||||
AppBar,
|
||||
Box,
|
||||
Button,
|
||||
Drawer,
|
||||
Grid,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
SwipeableDrawer,
|
||||
Toolbar,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { IndexRoute, SettingsRoute, SettingsTwoFactorAuthenticationSubRoute } from "@constants/Routes";
|
||||
|
@ -33,8 +34,7 @@ const defaultDrawerWidth = 240;
|
|||
|
||||
const SettingsLayout = function (props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
const navigate = useRouterNavigate();
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.title) {
|
||||
|
@ -54,72 +54,179 @@ const SettingsLayout = function (props: Props) {
|
|||
|
||||
const drawerWidth = props.drawerWidth === undefined ? defaultDrawerWidth : props.drawerWidth;
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppBar position="fixed" sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}>
|
||||
<Toolbar variant="dense">
|
||||
<Typography style={{ flexGrow: 1 }}>Authelia {translate("Settings")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
onClick={() => {
|
||||
navigate(IndexRoute);
|
||||
}}
|
||||
>
|
||||
{translate("Close")}
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: "border-box" },
|
||||
}}
|
||||
>
|
||||
<Toolbar variant="dense" />
|
||||
<Box sx={{ overflow: "auto" }}>
|
||||
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>
|
||||
<SettingsMenuItem pathname={SettingsRoute} text={translate("Overview")} icon={<Dashboard />} />
|
||||
<SettingsMenuItem
|
||||
pathname={`${SettingsRoute}${SettingsTwoFactorAuthenticationSubRoute}`}
|
||||
text={translate("Two-Factor Authentication")}
|
||||
icon={<SystemSecurityUpdateGoodIcon />}
|
||||
/>
|
||||
{navItems.map((item) => (
|
||||
<DrawerNavItem key={item.keyname} text={item.text} pathname={item.pathname} icon={item.icon} />
|
||||
))}
|
||||
</List>
|
||||
</Box>
|
||||
</Drawer>
|
||||
<Grid container id={props.id} spacing={0}>
|
||||
<Grid item xs={12}>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppBar component={"nav"}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
onClick={handleToggleDrawer}
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant="h6"
|
||||
component={"div"}
|
||||
sx={{ flexGrow: 1, display: { xs: "none", sm: drawerOpen ? "none" : "block" } }}
|
||||
>
|
||||
{translate("Settings")}
|
||||
</Typography>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Box component={"nav"}>
|
||||
<SwipeableDrawer
|
||||
container={container}
|
||||
anchor={"left"}
|
||||
open={drawerOpen}
|
||||
onOpen={handleToggleDrawer}
|
||||
onClose={handleToggleDrawer}
|
||||
ModalProps={{
|
||||
keepMounted: true,
|
||||
}}
|
||||
sx={{
|
||||
display: { xs: "block" },
|
||||
"& .MuiDrawer-paper": { boxSizing: "border-box", width: drawerWidth },
|
||||
}}
|
||||
>
|
||||
{drawer}
|
||||
</SwipeableDrawer>
|
||||
</Box>
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||
<Toolbar variant="dense" />
|
||||
<Toolbar />
|
||||
{props.children}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsLayout;
|
||||
|
||||
interface SettingsMenuItemProps {
|
||||
pathname: string;
|
||||
interface NavItem {
|
||||
keyname?: 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 navigate = useRouterNavigate();
|
||||
|
||||
const handleOnClick = useCallback(() => {
|
||||
if (selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(props.pathname);
|
||||
}, [navigate, props, selected]);
|
||||
|
||||
return (
|
||||
<ListItem disablePadding onClick={!selected ? () => navigate(props.pathname) : undefined}>
|
||||
<ListItem disablePadding onClick={handleOnClick}>
|
||||
<ListItemButton selected={selected}>
|
||||
<ListItemIcon>{props.icon}</ListItemIcon>
|
||||
{props.icon ? <ListItemIcon>{props.icon}</ListItemIcon> : null}
|
||||
<ListItemText primary={props.text} />
|
||||
</ListItemButton>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
*/
|
||||
|
|
|
@ -11,7 +11,6 @@ import { deleteUserTOTPConfiguration } from "@services/UserInfoTOTPConfiguration
|
|||
import DeleteDialog from "@views/Settings/TwoFactorAuthentication/DeleteDialog";
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
config: UserInfoTOTPConfiguration;
|
||||
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",
|
||||
)}
|
||||
/>
|
||||
<Stack direction="row" spacing={1} alignItems="center">
|
||||
<Stack direction={"row"} spacing={1} alignItems={"center"}>
|
||||
<QrCode2 fontSize="large" />
|
||||
<Stack spacing={0} sx={{ minWidth: 400 }}>
|
||||
<Box>
|
||||
<Typography display="inline" sx={{ fontWeight: "bold" }}>
|
||||
<Typography display={"inline"} sx={{ fontWeight: "bold" }}>
|
||||
{props.config.issuer}
|
||||
</Typography>
|
||||
<Typography display="inline" variant="body2">
|
||||
<Typography display={"inline"} variant={"body2"}>
|
||||
{" (" +
|
||||
translate("{{algorithm}}, {{digits}} digits, {{seconds}} seconds", {
|
||||
algorithm: toAlgorithmString(props.config.algorithm),
|
||||
|
@ -87,7 +86,7 @@ export default function TOTPDevice(props: Props) {
|
|||
</Typography>
|
||||
</Box>
|
||||
<Typography variant={"caption"}>
|
||||
{translate("Added", {
|
||||
{translate("Added when", {
|
||||
when: props.config.created_at,
|
||||
formatParams: {
|
||||
when: {
|
||||
|
@ -103,7 +102,7 @@ export default function TOTPDevice(props: Props) {
|
|||
<Typography variant={"caption"}>
|
||||
{props.config.last_used_at === undefined
|
||||
? translate("Never used")
|
||||
: translate("Last Used", {
|
||||
: translate("Last Used when", {
|
||||
when: props.config.last_used_at,
|
||||
formatParams: {
|
||||
when: {
|
||||
|
@ -117,7 +116,6 @@ export default function TOTPDevice(props: Props) {
|
|||
})}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Tooltip title={translate("Remove the Time-based One Time Password configuration")}>
|
||||
<Button
|
||||
variant={"outlined"}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
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 { UserInfoTOTPConfiguration } from "@models/TOTPConfiguration";
|
||||
|
@ -26,14 +27,14 @@ export default function TOTPPanel(props: Props) {
|
|||
props.handleRefreshState();
|
||||
}}
|
||||
/>
|
||||
<Paper variant="outlined" sx={{ p: 3 }}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h5">{translate("One Time Password")}</Typography>
|
||||
<Paper variant={"outlined"}>
|
||||
<Grid container spacing={2} padding={2}>
|
||||
<Grid xs={12} lg={8}>
|
||||
<Typography variant={"h5"}>{translate("One Time Password")}</Typography>
|
||||
</Grid>
|
||||
{props.config === undefined || props.config === null ? (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Grid xs={2}>
|
||||
<Tooltip
|
||||
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>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid xs={12}>
|
||||
<Typography variant={"subtitle2"}>
|
||||
{translate(
|
||||
"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>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={2}>
|
||||
<TOTPDevice index={0} config={props.config} handleRefresh={props.handleRefreshState} />
|
||||
<Grid xs={12}>
|
||||
<Stack spacing={3}>
|
||||
<TOTPDevice config={props.config} handleRefresh={props.handleRefreshState} />
|
||||
</Stack>
|
||||
</Grid>
|
||||
)}
|
||||
|
|
|
@ -2,6 +2,7 @@ import React, { Fragment, useCallback, useEffect, useState } from "react";
|
|||
|
||||
import { IconDefinition, faCopy, faKey, faTimesCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Visibility } from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
|
@ -9,11 +10,11 @@ import {
|
|||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
FormLabel,
|
||||
Grid,
|
||||
IconButton,
|
||||
Link,
|
||||
Radio,
|
||||
|
@ -26,6 +27,7 @@ import {
|
|||
Typography,
|
||||
} from "@mui/material";
|
||||
import { red } from "@mui/material/colors";
|
||||
import Grid from "@mui/material/Unstable_Grid2";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import classnames from "classnames";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
|
@ -69,6 +71,7 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
const [hasErrored, setHasErrored] = useState(false);
|
||||
const [dialValue, setDialValue] = useState("");
|
||||
const [dialState, setDialState] = useState(State.Idle);
|
||||
const [totpSecretURLHidden, setTOTPSecretURLHidden] = useState(true);
|
||||
|
||||
const resetStates = () => {
|
||||
setOptions(null);
|
||||
|
@ -85,6 +88,7 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
setActiveStep(0);
|
||||
setDialValue("");
|
||||
setDialState(State.Idle);
|
||||
setTOTPSecretURLHidden(true);
|
||||
};
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
|
@ -112,7 +116,7 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.open || activeStep !== 0) {
|
||||
if (!props.open || activeStep !== 0 || options !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -126,13 +130,14 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
setOptionPeriod(opts.period);
|
||||
setOptionPeriods(opts.periods.map((period) => period.toString()));
|
||||
})();
|
||||
}, [props.open, activeStep]);
|
||||
}, [props.open, activeStep, options]);
|
||||
|
||||
const handleSetStepPrevious = useCallback(() => {
|
||||
if (activeStep === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowAdvanced(false);
|
||||
setActiveStep((prevState) => (prevState -= 1));
|
||||
}, [activeStep]);
|
||||
|
||||
|
@ -141,6 +146,7 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
return;
|
||||
}
|
||||
|
||||
setShowAdvanced(false);
|
||||
setActiveStep((prevState) => (prevState += 1));
|
||||
}, [activeStep]);
|
||||
|
||||
|
@ -213,7 +219,6 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
function SecretButton(text: string | undefined, action: string, icon: IconDefinition) {
|
||||
return (
|
||||
<IconButton
|
||||
className={styles.secretButtons}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(`${text}`);
|
||||
|
@ -232,21 +237,20 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
return (
|
||||
<Fragment>
|
||||
{options === null ? (
|
||||
<Grid item xs={12}>
|
||||
<Grid xs={12}>
|
||||
<Typography>Loading...</Typography>
|
||||
</Grid>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Grid container>
|
||||
<Grid xs={12}>
|
||||
<Typography>{translate("To begin select next")}</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} hidden={hideAdvanced}>
|
||||
<Grid xs={12} hidden={hideAdvanced}>
|
||||
<Button variant={"outlined"} color={"warning"} onClick={toggleAdvanced}>
|
||||
{showAdvanced ? translate("Hide Advanced") : translate("Show Advanced")}
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
hidden={hideAdvanced || !showAdvanced}
|
||||
justifyContent={"center"}
|
||||
|
@ -330,15 +334,15 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
</RadioGroup>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
case 1:
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Box className={styles.googleAuthenticator}>
|
||||
<Grid xs={12}>
|
||||
<Box>
|
||||
<Typography className={styles.googleAuthenticatorText}>
|
||||
{translate("Need Google Authenticator?")}
|
||||
</Typography>
|
||||
|
@ -351,10 +355,10 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid xs={12}>
|
||||
<Box className={classnames(qrcodeFuzzyStyle, styles.qrcodeContainer)}>
|
||||
<Link href={totpSecretURL} underline="hover">
|
||||
<QRCodeSVG value={totpSecretURL} className={styles.qrcode} size={256} />
|
||||
<QRCodeSVG value={totpSecretURL} className={styles.qrcode} size={128} />
|
||||
{!hasErrored && totpIsLoading ? (
|
||||
<CircularProgress className={styles.loader} size={128} />
|
||||
) : null}
|
||||
|
@ -364,22 +368,20 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
</Link>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid xs={12}>
|
||||
<Grid container spacing={2} justifyContent={"center"}>
|
||||
<Grid item xs={12}>
|
||||
{totpSecretURL !== "empty" ? (
|
||||
<TextField
|
||||
id="secret-url"
|
||||
label={translate("Secret")}
|
||||
className={styles.secret}
|
||||
value={totpSecretURL}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
<Grid xs={2}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setTOTPSecretURLHidden((value) => !value);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
size="large"
|
||||
>
|
||||
<Visibility />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<Grid xs={2}>
|
||||
{totpSecretBase32
|
||||
? SecretButton(
|
||||
totpSecretBase32,
|
||||
|
@ -388,18 +390,31 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
)
|
||||
: null}
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
{totpSecretURL !== "empty"
|
||||
<Grid xs={2}>
|
||||
{totpSecretURL !== ""
|
||||
? SecretButton(totpSecretURL, translate("OTP URL copied to clipboard"), faCopy)
|
||||
: null}
|
||||
</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>
|
||||
</Fragment>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Grid xs={12}>
|
||||
<OTPDial
|
||||
passcode={dialValue}
|
||||
state={dialState}
|
||||
|
@ -416,8 +431,11 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
<Dialog open={props.open} onClose={handleOnClose} maxWidth={"xs"} fullWidth={true}>
|
||||
<DialogTitle>{translate("Register One Time Password (TOTP)")}</DialogTitle>
|
||||
<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 item xs={12}>
|
||||
<Grid xs={12}>
|
||||
<Stepper activeStep={activeStep}>
|
||||
{steps.map((label, index) => {
|
||||
const stepProps: { completed?: boolean } = {};
|
||||
|
@ -432,7 +450,7 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
})}
|
||||
</Stepper>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid xs={12}>
|
||||
<Grid container spacing={2} paddingY={3} justifyContent={"center"}>
|
||||
{renderStep(activeStep)}
|
||||
</Grid>
|
||||
|
@ -440,23 +458,13 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
variant={"outlined"}
|
||||
color={"primary"}
|
||||
onClick={handleSetStepPrevious}
|
||||
disabled={activeStep === 0}
|
||||
>
|
||||
<Button color={"primary"} onClick={handleSetStepPrevious} disabled={activeStep === 0}>
|
||||
{translate("Previous")}
|
||||
</Button>
|
||||
<Button variant={"outlined"} color={"primary"} onClick={handleClose}>
|
||||
<Button color={"error"} onClick={handleClose}>
|
||||
{translate("Cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant={"outlined"}
|
||||
color={"primary"}
|
||||
onClick={handleSetStepNext}
|
||||
disabled={activeStep === steps.length - 1}
|
||||
>
|
||||
<Button color={"primary"} onClick={handleSetStepNext} disabled={activeStep === steps.length - 1}>
|
||||
{translate("Next")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
@ -465,10 +473,6 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingBottom: theme.spacing(4),
|
||||
},
|
||||
qrcode: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
|
@ -483,15 +487,10 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
marginBottom: theme.spacing(1),
|
||||
width: "256px",
|
||||
},
|
||||
googleAuthenticator: {},
|
||||
googleAuthenticatorText: {
|
||||
fontSize: theme.typography.fontSize * 0.8,
|
||||
},
|
||||
googleAuthenticatorBadges: {},
|
||||
secretButtons: {},
|
||||
doneButton: {
|
||||
width: "256px",
|
||||
},
|
||||
qrcodeContainer: {
|
||||
position: "relative",
|
||||
display: "inline-block",
|
||||
|
|
|
@ -4,7 +4,7 @@ import Grid from "@mui/material/Unstable_Grid2";
|
|||
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { useUserInfoPOST } from "@hooks/UserInfo";
|
||||
import { useUserInfoTOTPConfiguration, useUserInfoTOTPConfigurationOptional } from "@hooks/UserInfoTOTPConfiguration";
|
||||
import { useUserInfoTOTPConfigurationOptional } from "@hooks/UserInfoTOTPConfiguration";
|
||||
import { useUserWebAuthnDevices } from "@hooks/WebAuthnDevices";
|
||||
import TOTPPanel from "@views/Settings/TwoFactorAuthentication/TOTPPanel";
|
||||
import WebAuthnDevicesPanel from "@views/Settings/TwoFactorAuthentication/WebAuthnDevicesPanel";
|
||||
|
|
Loading…
Reference in New Issue