fix: remove unused elements

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
feat-otp-email-verify
James Elliott 2023-04-21 23:25:09 +10:00
parent 058edbe796
commit 56aeb1bd86
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
6 changed files with 17 additions and 242 deletions

View File

@ -27,7 +27,6 @@ import {
getResetPasswordCustomURL, getResetPasswordCustomURL,
getTheme, getTheme,
} from "@utils/Configuration"; } from "@utils/Configuration";
import RegisterOneTimePassword from "@views/DeviceRegistration/RegisterOneTimePassword";
import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage"; import BaseLoadingPage from "@views/LoadingPage/BaseLoadingPage";
import ConsentView from "@views/LoginPortal/ConsentView/ConsentView"; import ConsentView from "@views/LoginPortal/ConsentView/ConsentView";
import LoginPortal from "@views/LoginPortal/LoginPortal"; import LoginPortal from "@views/LoginPortal/LoginPortal";
@ -90,7 +89,6 @@ const App: React.FC<Props> = (props: Props) => {
<Routes> <Routes>
<Route path={ResetPasswordStep1Route} element={<ResetPasswordStep1 />} /> <Route path={ResetPasswordStep1Route} element={<ResetPasswordStep1 />} />
<Route path={ResetPasswordStep2Route} element={<ResetPasswordStep2 />} /> <Route path={ResetPasswordStep2Route} element={<ResetPasswordStep2 />} />
<Route path={RegisterOneTimePasswordRoute} element={<RegisterOneTimePassword />} />
<Route path={LogoutRoute} element={<SignOut />} /> <Route path={LogoutRoute} element={<SignOut />} />
<Route path={ConsentRoute} element={<ConsentView />} /> <Route path={ConsentRoute} element={<ConsentView />} />
<Route path={`${SettingsRoute}/*`} element={<SettingsRouter />} /> <Route path={`${SettingsRoute}/*`} element={<SettingsRouter />} />

View File

@ -8,8 +8,6 @@ const basePath = getBasePath();
export const ConsentPath = basePath + "/api/oidc/consent"; export const ConsentPath = basePath + "/api/oidc/consent";
export const FirstFactorPath = basePath + "/api/firstfactor"; 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 TOTPRegistrationOptionsPath = basePath + "/api/secondfactor/totp/register/options";
export const TOTPRegistrationPath = basePath + "/api/secondfactor/totp/register"; export const TOTPRegistrationPath = basePath + "/api/secondfactor/totp/register";

View File

@ -1,19 +1,11 @@
import { CompleteTOTPRegistrationPath, InitiateTOTPRegistrationPath, TOTPRegistrationPath } from "@services/Api"; import { TOTPRegistrationPath } from "@services/Api";
import { Post, PostWithOptionalResponse, Put } from "@services/Client"; import { Put } from "@services/Client";
export async function initiateTOTPRegistrationProcess() {
await PostWithOptionalResponse(InitiateTOTPRegistrationPath);
}
interface CompleteTOTPRegistrationResponse { interface CompleteTOTPRegistrationResponse {
base32_secret: string; base32_secret: string;
otpauth_url: 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) { export async function getTOTPSecret(algorithm: string, length: number, period: number) {
return Put<CompleteTOTPRegistrationResponse>(TOTPRegistrationPath, { return Put<CompleteTOTPRegistrationResponse>(TOTPRegistrationPath, {
algorithm: algorithm, algorithm: algorithm,

View File

@ -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",
},
}));

View File

@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next";
import { Route, Routes, useNavigate } from "react-router-dom"; import { Route, Routes, useNavigate } from "react-router-dom";
import { import {
RegisterOneTimePasswordRoute,
SecondFactorPushSubRoute, SecondFactorPushSubRoute,
SecondFactorTOTPSubRoute, SecondFactorTOTPSubRoute,
SecondFactorWebAuthnSubRoute, SecondFactorWebAuthnSubRoute,
@ -19,7 +18,6 @@ import LoginLayout from "@layouts/LoginLayout";
import { Configuration } from "@models/Configuration"; import { Configuration } from "@models/Configuration";
import { SecondFactorMethod } from "@models/Methods"; import { SecondFactorMethod } from "@models/Methods";
import { UserInfo } from "@models/UserInfo"; import { UserInfo } from "@models/UserInfo";
import { initiateTOTPRegistrationProcess } from "@services/RegisterDevice";
import { AuthenticationLevel } from "@services/State"; import { AuthenticationLevel } from "@services/State";
import { setPreferred2FAMethod } from "@services/UserInfo"; import { setPreferred2FAMethod } from "@services/UserInfo";
import { isWebAuthnSupported } from "@services/WebAuthn"; import { isWebAuthnSupported } from "@services/WebAuthn";
@ -42,8 +40,7 @@ const SecondFactorForm = function (props: Props) {
const styles = useStyles(); const styles = useStyles();
const navigate = useNavigate(); const navigate = useNavigate();
const [methodSelectionOpen, setMethodSelectionOpen] = useState(false); const [methodSelectionOpen, setMethodSelectionOpen] = useState(false);
const { createInfoNotification, createErrorNotification } = useNotifications(); const { createErrorNotification } = useNotifications();
const [registrationInProgress, setRegistrationInProgress] = useState(false);
const [stateWebAuthnSupported, setStateWebAuthnSupported] = useState(false); const [stateWebAuthnSupported, setStateWebAuthnSupported] = useState(false);
const { t: translate } = useTranslation(); const { t: translate } = useTranslation();
@ -51,27 +48,6 @@ const SecondFactorForm = function (props: Props) {
setStateWebAuthnSupported(isWebAuthnSupported()); setStateWebAuthnSupported(isWebAuthnSupported());
}, [setStateWebAuthnSupported]); }, [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 = () => { const handleMethodSelectionClick = () => {
setMethodSelectionOpen(true); setMethodSelectionOpen(true);
}; };
@ -129,10 +105,9 @@ const SecondFactorForm = function (props: Props) {
authenticationLevel={props.authenticationLevel} authenticationLevel={props.authenticationLevel}
// Whether the user has a TOTP secret registered already // Whether the user has a TOTP secret registered already
registered={props.userInfo.has_totp} registered={props.userInfo.has_totp}
onRegisterClick={initiateRegistration( onRegisterClick={() => {
initiateTOTPRegistrationProcess, navigate(`${SettingsRoute}${SettingsTwoFactorAuthenticationSubRoute}`);
RegisterOneTimePasswordRoute, }}
)}
onSignInError={(err) => createErrorNotification(err.message)} onSignInError={(err) => createErrorNotification(err.message)}
onSignInSuccess={props.onAuthenticationSuccess} onSignInSuccess={props.onAuthenticationSuccess}
/> />

View File

@ -107,6 +107,11 @@ export default function TOTPRegisterDialogController(props: Props) {
})(); })();
}, [totpSecretURL, props]); }, [totpSecretURL, props]);
const handleFinished = useCallback(() => {
props.setClosed();
resetStates();
}, [props]);
const handleOnClose = () => { const handleOnClose = () => {
if (!props.open) { if (!props.open) {
return; return;
@ -191,8 +196,12 @@ export default function TOTPRegisterDialogController(props: Props) {
setDialState(State.InProgress); setDialState(State.InProgress);
try { try {
await completeTOTPRegister(dialValue); const registerValue = dialValue;
setDialState(State.Success); setDialValue("");
await completeTOTPRegister(registerValue);
handleFinished();
} catch (err) { } catch (err) {
console.error(err); console.error(err);
setDialState(State.Failure); setDialState(State.Failure);