fix: remove unused elements
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>feat-otp-email-verify
parent
058edbe796
commit
56aeb1bd86
|
@ -27,7 +27,6 @@ import {
|
|||
getResetPasswordCustomURL,
|
||||
getTheme,
|
||||
} from "@utils/Configuration";
|
||||
import RegisterOneTimePassword from "@views/DeviceRegistration/RegisterOneTimePassword";
|
||||
import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage";
|
||||
import ConsentView from "@views/LoginPortal/ConsentView/ConsentView";
|
||||
import LoginPortal from "@views/LoginPortal/LoginPortal";
|
||||
|
@ -90,7 +89,6 @@ const App: React.FC<Props> = (props: Props) => {
|
|||
<Routes>
|
||||
<Route path={ResetPasswordStep1Route} element={<ResetPasswordStep1 />} />
|
||||
<Route path={ResetPasswordStep2Route} element={<ResetPasswordStep2 />} />
|
||||
<Route path={RegisterOneTimePasswordRoute} element={<RegisterOneTimePassword />} />
|
||||
<Route path={LogoutRoute} element={<SignOut />} />
|
||||
<Route path={ConsentRoute} element={<ConsentView />} />
|
||||
<Route path={`${SettingsRoute}/*`} element={<SettingsRouter />} />
|
||||
|
|
|
@ -8,8 +8,6 @@ const basePath = getBasePath();
|
|||
export const ConsentPath = basePath + "/api/oidc/consent";
|
||||
|
||||
export const FirstFactorPath = basePath + "/api/firstfactor";
|
||||
export const InitiateTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/start";
|
||||
export const CompleteTOTPRegistrationPath = basePath + "/api/secondfactor/totp/identity/finish";
|
||||
|
||||
export const TOTPRegistrationOptionsPath = basePath + "/api/secondfactor/totp/register/options";
|
||||
export const TOTPRegistrationPath = basePath + "/api/secondfactor/totp/register";
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
import { CompleteTOTPRegistrationPath, InitiateTOTPRegistrationPath, TOTPRegistrationPath } from "@services/Api";
|
||||
import { Post, PostWithOptionalResponse, Put } from "@services/Client";
|
||||
|
||||
export async function initiateTOTPRegistrationProcess() {
|
||||
await PostWithOptionalResponse(InitiateTOTPRegistrationPath);
|
||||
}
|
||||
import { TOTPRegistrationPath } from "@services/Api";
|
||||
import { Put } from "@services/Client";
|
||||
|
||||
interface CompleteTOTPRegistrationResponse {
|
||||
base32_secret: string;
|
||||
otpauth_url: string;
|
||||
}
|
||||
|
||||
export async function completeTOTPRegistrationProcess(processToken: string) {
|
||||
return Post<CompleteTOTPRegistrationResponse>(CompleteTOTPRegistrationPath, { token: processToken });
|
||||
}
|
||||
|
||||
export async function getTOTPSecret(algorithm: string, length: number, period: number) {
|
||||
return Put<CompleteTOTPRegistrationResponse>(TOTPRegistrationPath, {
|
||||
algorithm: algorithm,
|
||||
|
|
|
@ -1,197 +0,0 @@
|
|||
import React, { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { IconDefinition, faCopy, faKey, faTimesCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Box, Button, CircularProgress, IconButton, Link, TextField, Theme, Typography } from "@mui/material";
|
||||
import { red } from "@mui/material/colors";
|
||||
import makeStyles from "@mui/styles/makeStyles";
|
||||
import classnames from "classnames";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import AppStoreBadges from "@components/AppStoreBadges";
|
||||
import { GoogleAuthenticator } from "@constants/constants";
|
||||
import { IndexRoute } from "@constants/Routes";
|
||||
import { IdentityToken } from "@constants/SearchParams";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import LoginLayout from "@layouts/LoginLayout";
|
||||
import { completeTOTPRegistrationProcess } from "@services/RegisterDevice";
|
||||
|
||||
const RegisterOneTimePassword = function () {
|
||||
const { t: translate } = useTranslation();
|
||||
|
||||
const styles = useStyles();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { createSuccessNotification, createErrorNotification } = useNotifications();
|
||||
|
||||
// The secret retrieved from the API is all is ok.
|
||||
const [secretURL, setSecretURL] = useState("empty");
|
||||
const [secretBase32, setSecretBase32] = useState(undefined as string | undefined);
|
||||
const [hasErrored, setHasErrored] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Get the token from the query param to give it back to the API when requesting
|
||||
// the secret for OTP.
|
||||
const processToken = useQueryParam(IdentityToken);
|
||||
|
||||
const handleDoneClick = () => {
|
||||
navigate(IndexRoute);
|
||||
};
|
||||
|
||||
const completeRegistrationProcess = useCallback(async () => {
|
||||
if (!processToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const secret = await completeTOTPRegistrationProcess(processToken);
|
||||
setSecretURL(secret.otpauth_url);
|
||||
setSecretBase32(secret.base32_secret);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if ((err as Error).message.includes("Request failed with status code 403")) {
|
||||
createErrorNotification(
|
||||
translate(
|
||||
"You must open the link from the same device and browser that initiated the registration process",
|
||||
),
|
||||
);
|
||||
} else {
|
||||
createErrorNotification(
|
||||
translate("Failed to register device, the provided link is expired or has already been used"),
|
||||
);
|
||||
}
|
||||
setHasErrored(true);
|
||||
}
|
||||
setIsLoading(false);
|
||||
}, [processToken, createErrorNotification, translate]);
|
||||
|
||||
useEffect(() => {
|
||||
completeRegistrationProcess();
|
||||
}, [completeRegistrationProcess]);
|
||||
|
||||
function SecretButton(text: string | undefined, action: string, icon: IconDefinition) {
|
||||
return (
|
||||
<IconButton
|
||||
className={styles.secretButtons}
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(`${text}`);
|
||||
createSuccessNotification(`${action}`);
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
const qrcodeFuzzyStyle = isLoading || hasErrored ? styles.fuzzy : undefined;
|
||||
|
||||
return (
|
||||
<LoginLayout title={translate("Scan QR Code")}>
|
||||
<Box className={styles.root}>
|
||||
<Box className={styles.googleAuthenticator}>
|
||||
<Typography className={styles.googleAuthenticatorText}>
|
||||
{translate("Need Google Authenticator?")}
|
||||
</Typography>
|
||||
<AppStoreBadges
|
||||
iconSize={128}
|
||||
targetBlank
|
||||
className={styles.googleAuthenticatorBadges}
|
||||
googlePlayLink={GoogleAuthenticator.googlePlay}
|
||||
appleStoreLink={GoogleAuthenticator.appleStore}
|
||||
/>
|
||||
</Box>
|
||||
<Box className={classnames(qrcodeFuzzyStyle, styles.qrcodeContainer)}>
|
||||
<Link href={secretURL} underline="hover">
|
||||
<QRCodeSVG value={secretURL} className={styles.qrcode} size={256} />
|
||||
{!hasErrored && isLoading ? <CircularProgress className={styles.loader} size={128} /> : null}
|
||||
{hasErrored ? <FontAwesomeIcon className={styles.failureIcon} icon={faTimesCircle} /> : null}
|
||||
</Link>
|
||||
</Box>
|
||||
<Box>
|
||||
{secretURL !== "empty" ? (
|
||||
<TextField
|
||||
id="secret-url"
|
||||
label={translate("Secret")}
|
||||
className={styles.secret}
|
||||
value={secretURL}
|
||||
InputProps={{
|
||||
readOnly: true,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{secretBase32
|
||||
? SecretButton(secretBase32, translate("OTP Secret copied to clipboard"), faKey)
|
||||
: null}
|
||||
{secretURL !== "empty"
|
||||
? SecretButton(secretURL, translate("OTP URL copied to clipboard"), faCopy)
|
||||
: null}
|
||||
</Box>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={styles.doneButton}
|
||||
onClick={handleDoneClick}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{translate("Done")}
|
||||
</Button>
|
||||
</Box>
|
||||
</LoginLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterOneTimePassword;
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
root: {
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingBottom: theme.spacing(4),
|
||||
},
|
||||
qrcode: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
padding: theme.spacing(),
|
||||
backgroundColor: "white",
|
||||
},
|
||||
fuzzy: {
|
||||
filter: "blur(10px)",
|
||||
},
|
||||
secret: {
|
||||
marginTop: theme.spacing(1),
|
||||
marginBottom: theme.spacing(1),
|
||||
width: "256px",
|
||||
},
|
||||
googleAuthenticator: {},
|
||||
googleAuthenticatorText: {
|
||||
fontSize: theme.typography.fontSize * 0.8,
|
||||
},
|
||||
googleAuthenticatorBadges: {},
|
||||
secretButtons: {
|
||||
width: "128px",
|
||||
},
|
||||
doneButton: {
|
||||
width: "256px",
|
||||
},
|
||||
qrcodeContainer: {
|
||||
position: "relative",
|
||||
display: "inline-block",
|
||||
},
|
||||
loader: {
|
||||
position: "absolute",
|
||||
top: "calc(128px - 64px)",
|
||||
left: "calc(128px - 64px)",
|
||||
color: "rgba(255, 255, 255, 0.5)",
|
||||
},
|
||||
failureIcon: {
|
||||
position: "absolute",
|
||||
top: "calc(128px - 64px)",
|
||||
left: "calc(128px - 64px)",
|
||||
color: red[400],
|
||||
fontSize: "128px",
|
||||
},
|
||||
}));
|
|
@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next";
|
|||
import { Route, Routes, useNavigate } from "react-router-dom";
|
||||
|
||||
import {
|
||||
RegisterOneTimePasswordRoute,
|
||||
SecondFactorPushSubRoute,
|
||||
SecondFactorTOTPSubRoute,
|
||||
SecondFactorWebAuthnSubRoute,
|
||||
|
@ -19,7 +18,6 @@ import LoginLayout from "@layouts/LoginLayout";
|
|||
import { Configuration } from "@models/Configuration";
|
||||
import { SecondFactorMethod } from "@models/Methods";
|
||||
import { UserInfo } from "@models/UserInfo";
|
||||
import { initiateTOTPRegistrationProcess } from "@services/RegisterDevice";
|
||||
import { AuthenticationLevel } from "@services/State";
|
||||
import { setPreferred2FAMethod } from "@services/UserInfo";
|
||||
import { isWebAuthnSupported } from "@services/WebAuthn";
|
||||
|
@ -42,8 +40,7 @@ const SecondFactorForm = function (props: Props) {
|
|||
const styles = useStyles();
|
||||
const navigate = useNavigate();
|
||||
const [methodSelectionOpen, setMethodSelectionOpen] = useState(false);
|
||||
const { createInfoNotification, createErrorNotification } = useNotifications();
|
||||
const [registrationInProgress, setRegistrationInProgress] = useState(false);
|
||||
const { createErrorNotification } = useNotifications();
|
||||
const [stateWebAuthnSupported, setStateWebAuthnSupported] = useState(false);
|
||||
const { t: translate } = useTranslation();
|
||||
|
||||
|
@ -51,27 +48,6 @@ const SecondFactorForm = function (props: Props) {
|
|||
setStateWebAuthnSupported(isWebAuthnSupported());
|
||||
}, [setStateWebAuthnSupported]);
|
||||
|
||||
const initiateRegistration = (initiateRegistrationFunc: () => Promise<void>, redirectRoute: string) => {
|
||||
return async () => {
|
||||
if (props.authenticationLevel >= AuthenticationLevel.TwoFactor) {
|
||||
navigate(redirectRoute);
|
||||
} else {
|
||||
if (registrationInProgress) {
|
||||
return;
|
||||
}
|
||||
setRegistrationInProgress(true);
|
||||
try {
|
||||
await initiateRegistrationFunc();
|
||||
createInfoNotification(translate("An email has been sent to your address to complete the process"));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createErrorNotification(translate("There was a problem initiating the registration process"));
|
||||
}
|
||||
setRegistrationInProgress(false);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleMethodSelectionClick = () => {
|
||||
setMethodSelectionOpen(true);
|
||||
};
|
||||
|
@ -129,10 +105,9 @@ const SecondFactorForm = function (props: Props) {
|
|||
authenticationLevel={props.authenticationLevel}
|
||||
// Whether the user has a TOTP secret registered already
|
||||
registered={props.userInfo.has_totp}
|
||||
onRegisterClick={initiateRegistration(
|
||||
initiateTOTPRegistrationProcess,
|
||||
RegisterOneTimePasswordRoute,
|
||||
)}
|
||||
onRegisterClick={() => {
|
||||
navigate(`${SettingsRoute}${SettingsTwoFactorAuthenticationSubRoute}`);
|
||||
}}
|
||||
onSignInError={(err) => createErrorNotification(err.message)}
|
||||
onSignInSuccess={props.onAuthenticationSuccess}
|
||||
/>
|
||||
|
|
|
@ -107,6 +107,11 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
})();
|
||||
}, [totpSecretURL, props]);
|
||||
|
||||
const handleFinished = useCallback(() => {
|
||||
props.setClosed();
|
||||
resetStates();
|
||||
}, [props]);
|
||||
|
||||
const handleOnClose = () => {
|
||||
if (!props.open) {
|
||||
return;
|
||||
|
@ -191,8 +196,12 @@ export default function TOTPRegisterDialogController(props: Props) {
|
|||
setDialState(State.InProgress);
|
||||
|
||||
try {
|
||||
await completeTOTPRegister(dialValue);
|
||||
setDialState(State.Success);
|
||||
const registerValue = dialValue;
|
||||
setDialValue("");
|
||||
|
||||
await completeTOTPRegister(registerValue);
|
||||
|
||||
handleFinished();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setDialState(State.Failure);
|
||||
|
|
Loading…
Reference in New Issue