fix: remove unused elements
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/5053/head
parent
716b80e4cf
commit
5fc0ac98f0
|
@ -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 />} />
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 { 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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue