Merge orgin/master into feat-settings-ui
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>feat-otp-verification
commit
6c89ee1f9c
|
@ -70,7 +70,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
|||
},
|
||||
{
|
||||
ID: "b-client",
|
||||
Description: "Normal DisplayName",
|
||||
Description: "Normal Description",
|
||||
Secret: MustDecodeSecret("$plaintext$b-client-secret"),
|
||||
Policy: twofactor,
|
||||
RedirectURIs: []string{
|
||||
|
|
|
@ -3,50 +3,50 @@
|
|||
"Add": "Add",
|
||||
"Add Credential": "Add Credential",
|
||||
"Added": "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",
|
||||
"Authenticator GUID": "Authenticator GUID",
|
||||
"Cancel": "Cancel",
|
||||
"Click to add a Webauthn credential to your account": "Click to add a Webauthn credential to your account",
|
||||
"Click to add a WebAuthn credential to your account": "Click to add a WebAuthn credential to your account",
|
||||
"Click to copy the": "Click to copy the",
|
||||
"Clone Warning": "Clone Warning",
|
||||
"Created": "Created",
|
||||
"Delete": "Delete",
|
||||
"Details": "Details",
|
||||
"Display extended information for this Webauthn credential": "Display extended information for this Webauthn credential",
|
||||
"Display extended information for this WebAuthn credential": "Display extended information for this WebAuthn credential",
|
||||
"Edit": "Edit",
|
||||
"Edit information for this Webauthn credential": "Edit information for this Webauthn credential",
|
||||
"Edit Webauthn Credential": "Edit Webauthn Credential",
|
||||
"Edit information for this WebAuthn credential": "Edit information for this WebAuthn credential",
|
||||
"Edit WebAuthn Credential": "Edit WebAuthn Credential",
|
||||
"Enabled": "Enabled",
|
||||
"Enter a new name for this Webauthn credential": "Enter a new name for this Webauthn credential:",
|
||||
"Enter a new name for this WebAuthn credential": "Enter a new name for this WebAuthn 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",
|
||||
"Last Used": "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 Registered WebAuthn Credentials": "No Registered WebAuthn Credentials",
|
||||
"Overview": "Overview",
|
||||
"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",
|
||||
"Relying Party ID": "Relying Party ID",
|
||||
"Remove": "Remove",
|
||||
"Remove this Webauthn credential": "Remove this Webauthn credential",
|
||||
"Remove Webauthn Credential": "Remove Webauthn Credential",
|
||||
"Remove this WebAuthn credential": "Remove this WebAuthn credential",
|
||||
"Remove WebAuthn Credential": "Remove WebAuthn Credential",
|
||||
"Settings": "Settings",
|
||||
"Successfully deleted the Webauthn credential": "Successfully deleted the Webauthn credential",
|
||||
"Successfully updated the Webauthn credential": "Successfully updated the Webauthn credential",
|
||||
"There was a problem deleting the Webauthn credential": "There was a problem deleting the Webauthn credential",
|
||||
"There was a problem updating the Webauthn credential": "There was a problem updating the Webauthn credential",
|
||||
"Successfully deleted the WebAuthn credential": "Successfully deleted the WebAuthn credential",
|
||||
"Successfully updated the WebAuthn credential": "Successfully updated the WebAuthn credential",
|
||||
"There was a problem deleting the WebAuthn credential": "There was a problem deleting the WebAuthn credential",
|
||||
"There was a problem updating the WebAuthn credential": "There was a problem updating the WebAuthn credential",
|
||||
"Transports": "Transports",
|
||||
"Two-Factor Authentication": "Two-Factor Authentication",
|
||||
"Usage Count": "Usage Count",
|
||||
"Webauthn Credential Details": "Webauthn Credential Details",
|
||||
"Webauthn Credentials": "Webauthn Credentials",
|
||||
"WebAuthn Credential Details": "WebAuthn Credential Details",
|
||||
"WebAuthn Credentials": "WebAuthn Credentials",
|
||||
"Yes": "Yes",
|
||||
"You must have a higher authentication level to delete Webauthn credentials": "You must have a higher authentication level to delete Webauthn credentials",
|
||||
"You must be elevated to delete Webauthn credentials": "You must be elevated to delete Webauthn credentials",
|
||||
"You must have a higher authentication level to update Webauthn credentials": "You must have a higher authentication level to update Webauthn credentials",
|
||||
"You must be elevated to update Webauthn credentials": "You must be elevated to update Webauthn credentials"
|
||||
"You must have a higher authentication level to delete WebAuthn credentials": "You must have a higher authentication level to delete WebAuthn credentials",
|
||||
"You must be elevated to delete WebAuthn credentials": "You must be elevated to delete WebAuthn credentials",
|
||||
"You must have a higher authentication level to update WebAuthn credentials": "You must have a higher authentication level to update WebAuthn credentials",
|
||||
"You must be elevated to update WebAuthn credentials": "You must be elevated to update WebAuthn credentials"
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ interface Props {
|
|||
timeout: number;
|
||||
}
|
||||
|
||||
export default function WebauthnRegisterIcon(props: Props) {
|
||||
export default function WebAuthnRegisterIcon(props: Props) {
|
||||
const theme = useTheme();
|
||||
const [timerPercent, triggerTimer] = useTimer(props.timeout);
|
||||
|
|
@ -7,15 +7,15 @@ import FailureIcon from "@components/FailureIcon";
|
|||
import FingerTouchIcon from "@components/FingerTouchIcon";
|
||||
import LinearProgressBar from "@components/LinearProgressBar";
|
||||
import { useTimer } from "@hooks/Timer";
|
||||
import { WebauthnTouchState } from "@models/Webauthn";
|
||||
import { WebAuthnTouchState } from "@models/WebAuthn";
|
||||
import IconWithContext from "@views/LoginPortal/SecondFactor/IconWithContext";
|
||||
|
||||
interface Props {
|
||||
onRetryClick: () => void;
|
||||
webauthnTouchState: WebauthnTouchState;
|
||||
webauthnTouchState: WebAuthnTouchState;
|
||||
}
|
||||
|
||||
export default function WebauthnTryIcon(props: Props) {
|
||||
export default function WebAuthnTryIcon(props: Props) {
|
||||
const touchTimeout = 30;
|
||||
const theme = useTheme();
|
||||
const [timerPercent, triggerTimer, clearTimer] = useTimer(touchTimeout * 1000 - 500);
|
||||
|
@ -42,7 +42,7 @@ export default function WebauthnTryIcon(props: Props) {
|
|||
const touch = (
|
||||
<IconWithContext
|
||||
icon={<FingerTouchIcon size={64} animated strong />}
|
||||
className={props.webauthnTouchState === WebauthnTouchState.WaitTouch ? undefined : "hidden"}
|
||||
className={props.webauthnTouchState === WebAuthnTouchState.WaitTouch ? undefined : "hidden"}
|
||||
>
|
||||
<LinearProgressBar value={timerPercent} className={styles.progressBar} height={theme.spacing(2)} />
|
||||
</IconWithContext>
|
||||
|
@ -51,7 +51,7 @@ export default function WebauthnTryIcon(props: Props) {
|
|||
const failure = (
|
||||
<IconWithContext
|
||||
icon={<FailureIcon />}
|
||||
className={props.webauthnTouchState === WebauthnTouchState.Failure ? undefined : "hidden"}
|
||||
className={props.webauthnTouchState === WebAuthnTouchState.Failure ? undefined : "hidden"}
|
||||
>
|
||||
<Button color="secondary" onClick={handleRetryClick}>
|
||||
Retry
|
|
@ -3,7 +3,7 @@ export const AuthenticatedRoute: string = "/authenticated";
|
|||
export const ConsentRoute: string = "/consent";
|
||||
|
||||
export const SecondFactorRoute: string = "/2fa";
|
||||
export const SecondFactorWebauthnSubRoute: string = "/webauthn";
|
||||
export const SecondFactorWebAuthnSubRoute: string = "/webauthn";
|
||||
export const SecondFactorTOTPSubRoute: string = "/one-time-password";
|
||||
export const SecondFactorPushSubRoute: string = "/push-notification";
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export enum SecondFactorMethod {
|
||||
TOTP = 1,
|
||||
Webauthn,
|
||||
WebAuthn,
|
||||
MobilePush,
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export enum AttestationResult {
|
|||
FailureSyntax,
|
||||
FailureSupport,
|
||||
FailureUnknown,
|
||||
FailureWebauthnNotSupported,
|
||||
FailureWebAuthnNotSupported,
|
||||
FailureToken,
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ export enum AssertionResult {
|
|||
FailureSyntax,
|
||||
FailureUnknown,
|
||||
FailureUnknownSecurity,
|
||||
FailureWebauthnNotSupported,
|
||||
FailureWebAuthnNotSupported,
|
||||
FailureChallenge,
|
||||
FailureUnrecognized,
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export function AssertionResultFailureString(result: AssertionResult) {
|
|||
return "The server responded with an invalid Facet ID for the URL.";
|
||||
case AssertionResult.FailureSyntax:
|
||||
return "The assertion challenge was rejected as malformed or incompatible by your browser.";
|
||||
case AssertionResult.FailureWebauthnNotSupported:
|
||||
case AssertionResult.FailureWebAuthnNotSupported:
|
||||
return "Your browser does not support the WebAuthN protocol.";
|
||||
case AssertionResult.FailureUnrecognized:
|
||||
return "This device is not registered.";
|
||||
|
@ -85,7 +85,7 @@ export function AttestationResultFailureString(result: AttestationResult) {
|
|||
return "Your browser does not appear to support the configuration.";
|
||||
case AttestationResult.FailureSyntax:
|
||||
return "The attestation challenge was rejected as malformed or incompatible by your browser.";
|
||||
case AttestationResult.FailureWebauthnNotSupported:
|
||||
case AttestationResult.FailureWebAuthnNotSupported:
|
||||
return "Your browser does not support the WebAuthN protocol.";
|
||||
case AttestationResult.FailureUserConsent:
|
||||
return "You cancelled the attestation request.";
|
||||
|
@ -105,7 +105,7 @@ export interface AuthenticationResult {
|
|||
result: AssertionResult;
|
||||
}
|
||||
|
||||
export interface WebauthnDevice {
|
||||
export interface WebAuthnDevice {
|
||||
id: string;
|
||||
created_at: string;
|
||||
last_used_at?: string;
|
||||
|
@ -140,7 +140,7 @@ export function toTransportName(transport: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export enum WebauthnTouchState {
|
||||
export enum WebAuthnTouchState {
|
||||
WaitTouch = 1,
|
||||
InProgress = 2,
|
||||
Failure = 3,
|
|
@ -22,7 +22,7 @@ export function toEnum(method: Method2FA): SecondFactorMethod {
|
|||
case "totp":
|
||||
return SecondFactorMethod.TOTP;
|
||||
case "webauthn":
|
||||
return SecondFactorMethod.Webauthn;
|
||||
return SecondFactorMethod.WebAuthn;
|
||||
case "mobile_push":
|
||||
return SecondFactorMethod.MobilePush;
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ export function toString(method: SecondFactorMethod): Method2FA {
|
|||
switch (method) {
|
||||
case SecondFactorMethod.TOTP:
|
||||
return "totp";
|
||||
case SecondFactorMethod.Webauthn:
|
||||
case SecondFactorMethod.WebAuthn:
|
||||
return "webauthn";
|
||||
case SecondFactorMethod.MobilePush:
|
||||
return "mobile_push";
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { WebAuthnDevice } from "@models/WebAuthn";
|
||||
import { WebAuthnDevicesPath } from "@services/Api";
|
||||
import { GetWithOptionalData } from "@services/Client";
|
||||
|
||||
// getWebAuthnDevices returns the list of webauthn devices for the authenticated user.
|
||||
export async function getWebAuthnDevices(): Promise<WebAuthnDevice[] | null> {
|
||||
return GetWithOptionalData<WebAuthnDevice[] | null>(WebAuthnDevicesPath);
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { WebauthnDevice } from "@models/Webauthn";
|
||||
import { WebAuthnDevicesPath } from "@services/Api";
|
||||
import { GetWithOptionalData } from "@services/Client";
|
||||
|
||||
// getWebauthnDevices returns the list of webauthn devices for the authenticated user.
|
||||
export async function getWebauthnDevices(): Promise<WebauthnDevice[] | null> {
|
||||
return GetWithOptionalData<WebauthnDevice[] | null>(WebAuthnDevicesPath);
|
||||
}
|
|
@ -16,7 +16,7 @@ import {
|
|||
PublicKeyCredentialCreationOptionsStatus,
|
||||
PublicKeyCredentialRequestOptionsStatus,
|
||||
RegistrationResult,
|
||||
} from "@models/Webauthn";
|
||||
} from "@models/WebAuthn";
|
||||
import {
|
||||
AuthenticationOKResponse,
|
||||
OptionalDataServiceResponse,
|
||||
|
@ -28,7 +28,7 @@ import {
|
|||
} from "@services/Api";
|
||||
import { SignInResponse } from "@services/SignIn";
|
||||
|
||||
export function isWebauthnSecure(): boolean {
|
||||
export function isWebAuthnSecure(): boolean {
|
||||
if (window.isSecureContext) {
|
||||
return true;
|
||||
}
|
||||
|
@ -36,12 +36,12 @@ export function isWebauthnSecure(): boolean {
|
|||
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
||||
}
|
||||
|
||||
export function isWebauthnSupported(): boolean {
|
||||
export function isWebAuthnSupported(): boolean {
|
||||
return window?.PublicKeyCredential !== undefined && typeof window.PublicKeyCredential === "function";
|
||||
}
|
||||
|
||||
export async function isWebauthnPlatformAuthenticatorAvailable(): Promise<boolean> {
|
||||
if (!isWebauthnSupported()) {
|
||||
export async function isWebAuthnPlatformAuthenticatorAvailable(): Promise<boolean> {
|
||||
if (!isWebAuthnSupported()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,7 @@ export async function getAuthenticationOptions(): Promise<PublicKeyCredentialReq
|
|||
};
|
||||
}
|
||||
|
||||
export async function startWebauthnRegistration(options: PublicKeyCredentialCreationOptionsJSON) {
|
||||
export async function startWebAuthnRegistration(options: PublicKeyCredentialCreationOptionsJSON) {
|
||||
const result: RegistrationResult = {
|
||||
result: AttestationResult.Failure,
|
||||
};
|
|
@ -8,7 +8,7 @@ import {
|
|||
SecondFactorPushSubRoute,
|
||||
SecondFactorRoute,
|
||||
SecondFactorTOTPSubRoute,
|
||||
SecondFactorWebauthnSubRoute,
|
||||
SecondFactorWebAuthnSubRoute,
|
||||
} from "@constants/Routes";
|
||||
import { RedirectionURL } from "@constants/SearchParams";
|
||||
import { useConfiguration } from "@hooks/Configuration";
|
||||
|
@ -127,8 +127,8 @@ const LoginPortal = function (props: Props) {
|
|||
if (configuration.available_methods.size === 0) {
|
||||
navigate(AuthenticatedRoute, false);
|
||||
} else {
|
||||
if (userInfo.method === SecondFactorMethod.Webauthn) {
|
||||
navigate(`${SecondFactorRoute}${SecondFactorWebauthnSubRoute}`);
|
||||
if (userInfo.method === SecondFactorMethod.WebAuthn) {
|
||||
navigate(`${SecondFactorRoute}${SecondFactorWebAuthnSubRoute}`);
|
||||
} else if (userInfo.method === SecondFactorMethod.MobilePush) {
|
||||
navigate(`${SecondFactorRoute}${SecondFactorPushSubRoute}`);
|
||||
} else {
|
||||
|
|
|
@ -39,12 +39,12 @@ const MethodSelectionDialog = function (props: Props) {
|
|||
onClick={() => props.onClick(SecondFactorMethod.TOTP)}
|
||||
/>
|
||||
) : null}
|
||||
{props.methods.has(SecondFactorMethod.Webauthn) && props.webauthnSupported ? (
|
||||
{props.methods.has(SecondFactorMethod.WebAuthn) && props.webauthnSupported ? (
|
||||
<MethodItem
|
||||
id="webauthn-option"
|
||||
method={translate("Security Key - WebAuthN")}
|
||||
icon={<FingerTouchIcon size={32} />}
|
||||
onClick={() => props.onClick(SecondFactorMethod.Webauthn)}
|
||||
onClick={() => props.onClick(SecondFactorMethod.WebAuthn)}
|
||||
/>
|
||||
) : null}
|
||||
{props.methods.has(SecondFactorMethod.MobilePush) ? (
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
RegisterOneTimePasswordRoute,
|
||||
SecondFactorPushSubRoute,
|
||||
SecondFactorTOTPSubRoute,
|
||||
SecondFactorWebauthnSubRoute,
|
||||
SecondFactorWebAuthnSubRoute,
|
||||
SettingsRoute,
|
||||
SettingsTwoFactorAuthenticationSubRoute,
|
||||
LogoutRoute as SignOutRoute,
|
||||
|
@ -22,11 +22,11 @@ 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";
|
||||
import { isWebAuthnSupported } from "@services/WebAuthn";
|
||||
import MethodSelectionDialog from "@views/LoginPortal/SecondFactor/MethodSelectionDialog";
|
||||
import OneTimePasswordMethod from "@views/LoginPortal/SecondFactor/OneTimePasswordMethod";
|
||||
import PushNotificationMethod from "@views/LoginPortal/SecondFactor/PushNotificationMethod";
|
||||
import WebauthnMethod from "@views/LoginPortal/SecondFactor/WebauthnMethod";
|
||||
import WebAuthnMethod from "@views/LoginPortal/SecondFactor/WebAuthnMethod";
|
||||
|
||||
export interface Props {
|
||||
authenticationLevel: AuthenticationLevel;
|
||||
|
@ -44,12 +44,12 @@ const SecondFactorForm = function (props: Props) {
|
|||
const [methodSelectionOpen, setMethodSelectionOpen] = useState(false);
|
||||
const { createInfoNotification, createErrorNotification } = useNotifications();
|
||||
const [registrationInProgress, setRegistrationInProgress] = useState(false);
|
||||
const [webauthnSupported, setWebauthnSupported] = useState(false);
|
||||
const [stateWebAuthnSupported, setStateWebAuthnSupported] = useState(false);
|
||||
const { t: translate } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
setWebauthnSupported(isWebauthnSupported());
|
||||
}, [setWebauthnSupported]);
|
||||
setStateWebAuthnSupported(isWebAuthnSupported());
|
||||
}, [setStateWebAuthnSupported]);
|
||||
|
||||
const initiateRegistration = (initiateRegistrationFunc: () => Promise<void>, redirectRoute: string) => {
|
||||
return async () => {
|
||||
|
@ -102,7 +102,7 @@ const SecondFactorForm = function (props: Props) {
|
|||
<MethodSelectionDialog
|
||||
open={methodSelectionOpen}
|
||||
methods={props.configuration.available_methods}
|
||||
webauthnSupported={webauthnSupported}
|
||||
webauthnSupported={stateWebAuthnSupported}
|
||||
onClose={() => setMethodSelectionOpen(false)}
|
||||
onClick={handleMethodSelected}
|
||||
/>
|
||||
|
@ -139,12 +139,12 @@ const SecondFactorForm = function (props: Props) {
|
|||
}
|
||||
/>
|
||||
<Route
|
||||
path={SecondFactorWebauthnSubRoute}
|
||||
path={SecondFactorWebAuthnSubRoute}
|
||||
element={
|
||||
<WebauthnMethod
|
||||
<WebAuthnMethod
|
||||
id="webauthn-method"
|
||||
authenticationLevel={props.authenticationLevel}
|
||||
// Whether the user has a Webauthn device registered already
|
||||
// Whether the user has a WebAuthn device registered already
|
||||
registered={props.userInfo.has_webauthn}
|
||||
onRegisterClick={() => {
|
||||
navigate(`${SettingsRoute}${SettingsTwoFactorAuthenticationSubRoute}`);
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import WebauthnTryIcon from "@components/WebauthnTryIcon";
|
||||
import WebAuthnTryIcon from "@components/WebAuthnTryIcon";
|
||||
import { RedirectionURL } from "@constants/SearchParams";
|
||||
import { useIsMountedRef } from "@hooks/Mounted";
|
||||
import { useQueryParam } from "@hooks/QueryParam";
|
||||
import { useWorkflow } from "@hooks/Workflow";
|
||||
import { AssertionResult, AssertionResultFailureString, WebauthnTouchState } from "@models/Webauthn";
|
||||
import { AssertionResult, AssertionResultFailureString, WebAuthnTouchState } from "@models/WebAuthn";
|
||||
import { AuthenticationLevel } from "@services/State";
|
||||
import { getAuthenticationOptions, getAuthenticationResult, postAuthenticationResponse } from "@services/Webauthn";
|
||||
import { getAuthenticationOptions, getAuthenticationResult, postAuthenticationResponse } from "@services/WebAuthn";
|
||||
import MethodContainer, { State as MethodContainerState } from "@views/LoginPortal/SecondFactor/MethodContainer";
|
||||
|
||||
export interface Props {
|
||||
|
@ -20,8 +20,8 @@ export interface Props {
|
|||
onSignInSuccess: (redirectURL: string | undefined) => void;
|
||||
}
|
||||
|
||||
const WebauthnMethod = function (props: Props) {
|
||||
const [state, setState] = useState(WebauthnTouchState.WaitTouch);
|
||||
const WebAuthnMethod = function (props: Props) {
|
||||
const [state, setState] = useState(WebAuthnTouchState.WaitTouch);
|
||||
const redirectionURL = useQueryParam(RedirectionURL);
|
||||
const [workflow, workflowID] = useWorkflow();
|
||||
const mounted = useIsMountedRef();
|
||||
|
@ -37,11 +37,11 @@ const WebauthnMethod = function (props: Props) {
|
|||
}
|
||||
|
||||
try {
|
||||
setState(WebauthnTouchState.WaitTouch);
|
||||
setState(WebAuthnTouchState.WaitTouch);
|
||||
const optionsStatus = await getAuthenticationOptions();
|
||||
|
||||
if (optionsStatus.status !== 200 || optionsStatus.options == null) {
|
||||
setState(WebauthnTouchState.Failure);
|
||||
setState(WebAuthnTouchState.Failure);
|
||||
onSignInErrorCallback(new Error("Failed to initiate security key sign in process"));
|
||||
|
||||
return;
|
||||
|
@ -52,7 +52,7 @@ const WebauthnMethod = function (props: Props) {
|
|||
if (result.result !== AssertionResult.Success) {
|
||||
if (!mounted.current) return;
|
||||
|
||||
setState(WebauthnTouchState.Failure);
|
||||
setState(WebAuthnTouchState.Failure);
|
||||
|
||||
onSignInErrorCallback(new Error(AssertionResultFailureString(result.result)));
|
||||
|
||||
|
@ -61,14 +61,14 @@ const WebauthnMethod = function (props: Props) {
|
|||
|
||||
if (result.response == null) {
|
||||
onSignInErrorCallback(new Error("The browser did not respond with the expected attestation data."));
|
||||
setState(WebauthnTouchState.Failure);
|
||||
setState(WebAuthnTouchState.Failure);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mounted.current) return;
|
||||
|
||||
setState(WebauthnTouchState.InProgress);
|
||||
setState(WebAuthnTouchState.InProgress);
|
||||
|
||||
const response = await postAuthenticationResponse(result.response, redirectionURL, workflow, workflowID);
|
||||
|
||||
|
@ -80,14 +80,14 @@ const WebauthnMethod = function (props: Props) {
|
|||
if (!mounted.current) return;
|
||||
|
||||
onSignInErrorCallback(new Error("The server rejected the security key."));
|
||||
setState(WebauthnTouchState.Failure);
|
||||
setState(WebAuthnTouchState.Failure);
|
||||
} catch (err) {
|
||||
// If the request was initiated and the user changed 2FA method in the meantime,
|
||||
// the process is interrupted to avoid updating state of unmounted component.
|
||||
if (!mounted.current) return;
|
||||
console.error(err);
|
||||
onSignInErrorCallback(new Error("Failed to initiate security key sign in process"));
|
||||
setState(WebauthnTouchState.Failure);
|
||||
setState(WebAuthnTouchState.Failure);
|
||||
}
|
||||
}, [
|
||||
onSignInErrorCallback,
|
||||
|
@ -121,9 +121,9 @@ const WebauthnMethod = function (props: Props) {
|
|||
state={methodState}
|
||||
onRegisterClick={props.onRegisterClick}
|
||||
>
|
||||
<WebauthnTryIcon onRetryClick={doInitiateSignIn} webauthnTouchState={state} />
|
||||
<WebAuthnTryIcon onRetryClick={doInitiateSignIn} webauthnTouchState={state} />
|
||||
</MethodContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebauthnMethod;
|
||||
export default WebAuthnMethod;
|
|
@ -3,7 +3,7 @@ import React from "react";
|
|||
import { Grid } from "@mui/material";
|
||||
|
||||
import { AutheliaState } from "@services/State";
|
||||
import WebauthnDevices from "@views/Settings/TwoFactorAuthentication/WebauthnDevices";
|
||||
import WebAuthnDevices from "@views/Settings/TwoFactorAuthentication/WebAuthnDevices";
|
||||
|
||||
interface Props {
|
||||
state: AutheliaState;
|
||||
|
@ -13,7 +13,7 @@ export default function TwoFactorAuthSettings(props: Props) {
|
|||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<WebauthnDevices state={props.state} />
|
||||
<WebAuthnDevices state={props.state} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
|
|
@ -3,15 +3,15 @@ import React from "react";
|
|||
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { WebauthnDevice } from "@models/Webauthn";
|
||||
import { WebAuthnDevice } from "@models/WebAuthn";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
device: WebauthnDevice;
|
||||
device: WebAuthnDevice;
|
||||
handleClose: (ok: boolean) => void;
|
||||
}
|
||||
|
||||
export default function WebauthnDeviceDeleteDialog(props: Props) {
|
||||
export default function WebAuthnDeviceDeleteDialog(props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
const handleCancel = () => {
|
||||
|
@ -20,10 +20,10 @@ export default function WebauthnDeviceDeleteDialog(props: Props) {
|
|||
|
||||
return (
|
||||
<Dialog open={props.open} onClose={handleCancel}>
|
||||
<DialogTitle>{translate("Remove Webauthn Credential")}</DialogTitle>
|
||||
<DialogTitle>{translate("Remove WebAuthn Credential")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
{translate("Are you sure you want to remove the Webauthn credential from from your account", {
|
||||
{translate("Are you sure you want to remove the WebAuthn credential from from your account", {
|
||||
description: props.device.description,
|
||||
})}
|
||||
</DialogContentText>
|
|
@ -16,23 +16,23 @@ import {
|
|||
} from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { WebauthnDevice, toTransportName } from "@models/Webauthn";
|
||||
import { WebAuthnDevice, toTransportName } from "@models/WebAuthn";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
device: WebauthnDevice;
|
||||
device: WebAuthnDevice;
|
||||
handleClose: () => void;
|
||||
}
|
||||
|
||||
export default function WebauthnDetailsDeleteDialog(props: Props) {
|
||||
export default function WebAuthnDetailsDeleteDialog(props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onClose={props.handleClose}>
|
||||
<DialogTitle>{translate("Webauthn Credential Details")}</DialogTitle>
|
||||
<DialogTitle>{translate("WebAuthn Credential Details")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText sx={{ mb: 3 }}>
|
||||
{translate("Extended Webauthn credential information for security key", {
|
||||
{translate("Extended WebAuthn credential information for security key", {
|
||||
description: props.device.description,
|
||||
})}
|
||||
</DialogContentText>
|
|
@ -3,15 +3,15 @@ import React, { MutableRefObject, useRef, useState } from "react";
|
|||
import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { WebauthnDevice } from "@models/Webauthn";
|
||||
import { WebAuthnDevice } from "@models/WebAuthn";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
device: WebauthnDevice;
|
||||
device: WebAuthnDevice;
|
||||
handleClose: (ok: boolean, name: string) => void;
|
||||
}
|
||||
|
||||
export default function WebauthnDeviceEditDialog(props: Props) {
|
||||
export default function WebAuthnDeviceEditDialog(props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
const [deviceName, setName] = useState("");
|
||||
|
@ -34,9 +34,9 @@ export default function WebauthnDeviceEditDialog(props: Props) {
|
|||
|
||||
return (
|
||||
<Dialog open={props.open} onClose={handleCancel}>
|
||||
<DialogTitle>{translate("Edit Webauthn Credential")}</DialogTitle>
|
||||
<DialogTitle>{translate("Edit WebAuthn Credential")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>{translate("Enter a new name for this Webauthn credential")}</DialogContentText>
|
||||
<DialogContentText>{translate("Enter a new name for this WebAuthn credential")}</DialogContentText>
|
||||
<TextField
|
||||
autoFocus
|
||||
inputRef={nameRef}
|
|
@ -8,19 +8,19 @@ import { Box, Button, CircularProgress, Paper, Stack, Tooltip, Typography } from
|
|||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { WebauthnDevice } from "@models/Webauthn";
|
||||
import { deleteDevice, updateDevice } from "@services/Webauthn";
|
||||
import WebauthnDeviceDeleteDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceDeleteDialog";
|
||||
import WebauthnDeviceDetailsDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceDetailsDialog";
|
||||
import WebauthnDeviceEditDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceEditDialog";
|
||||
import { WebAuthnDevice } from "@models/WebAuthn";
|
||||
import { deleteDevice, updateDevice } from "@services/WebAuthn";
|
||||
import WebAuthnDeviceDeleteDialog from "@views/Settings/TwoFactorAuthentication/WebAuthnDeviceDeleteDialog";
|
||||
import WebAuthnDeviceDetailsDialog from "@views/Settings/TwoFactorAuthentication/WebAuthnDeviceDetailsDialog";
|
||||
import WebAuthnDeviceEditDialog from "@views/Settings/TwoFactorAuthentication/WebAuthnDeviceEditDialog";
|
||||
|
||||
interface Props {
|
||||
index: number;
|
||||
device: WebauthnDevice;
|
||||
device: WebAuthnDevice;
|
||||
handleEdit: () => void;
|
||||
}
|
||||
|
||||
export default function WebauthnDeviceItem(props: Props) {
|
||||
export default function WebAuthnDeviceItem(props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
const { createSuccessNotification, createErrorNotification } = useNotifications();
|
||||
|
@ -47,19 +47,19 @@ export default function WebauthnDeviceItem(props: Props) {
|
|||
|
||||
if (response.data.status === "KO") {
|
||||
if (response.data.elevation) {
|
||||
createErrorNotification(translate("You must be elevated to update Webauthn credentials"));
|
||||
createErrorNotification(translate("You must be elevated to update WebAuthn credentials"));
|
||||
} else if (response.data.authentication) {
|
||||
createErrorNotification(
|
||||
translate("You must have a higher authentication level to update Webauthn credentials"),
|
||||
translate("You must have a higher authentication level to update WebAuthn credentials"),
|
||||
);
|
||||
} else {
|
||||
createErrorNotification(translate("There was a problem updating the Webauthn credential"));
|
||||
createErrorNotification(translate("There was a problem updating the WebAuthn credential"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
createSuccessNotification(translate("Successfully updated the Webauthn credential"));
|
||||
createSuccessNotification(translate("Successfully updated the WebAuthn credential"));
|
||||
|
||||
props.handleEdit();
|
||||
};
|
||||
|
@ -79,19 +79,19 @@ export default function WebauthnDeviceItem(props: Props) {
|
|||
|
||||
if (response.data.status === "KO") {
|
||||
if (response.data.elevation) {
|
||||
createErrorNotification(translate("You must be elevated to delete Webauthn credentials"));
|
||||
createErrorNotification(translate("You must be elevated to delete WebAuthn credentials"));
|
||||
} else if (response.data.authentication) {
|
||||
createErrorNotification(
|
||||
translate("You must have a higher authentication level to delete Webauthn credentials"),
|
||||
translate("You must have a higher authentication level to delete WebAuthn credentials"),
|
||||
);
|
||||
} else {
|
||||
createErrorNotification(translate("There was a problem deleting the Webauthn credential"));
|
||||
createErrorNotification(translate("There was a problem deleting the WebAuthn credential"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
createSuccessNotification(translate("Successfully deleted the Webauthn credential"));
|
||||
createSuccessNotification(translate("Successfully deleted the WebAuthn credential"));
|
||||
|
||||
props.handleEdit();
|
||||
};
|
||||
|
@ -100,15 +100,15 @@ export default function WebauthnDeviceItem(props: Props) {
|
|||
<Fragment>
|
||||
<Paper variant="outlined">
|
||||
<Box sx={{ p: 3 }}>
|
||||
<WebauthnDeviceDetailsDialog
|
||||
<WebAuthnDeviceDetailsDialog
|
||||
device={props.device}
|
||||
open={showDialogDetails}
|
||||
handleClose={() => {
|
||||
setShowDialogDetails(false);
|
||||
}}
|
||||
/>
|
||||
<WebauthnDeviceEditDialog device={props.device} open={showDialogEdit} handleClose={handleEdit} />
|
||||
<WebauthnDeviceDeleteDialog
|
||||
<WebAuthnDeviceEditDialog device={props.device} open={showDialogEdit} handleClose={handleEdit} />
|
||||
<WebAuthnDeviceDeleteDialog
|
||||
device={props.device}
|
||||
open={showDialogDelete}
|
||||
handleClose={handleDelete}
|
||||
|
@ -157,7 +157,7 @@ export default function WebauthnDeviceItem(props: Props) {
|
|||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Tooltip title={translate("Display extended information for this Webauthn credential")}>
|
||||
<Tooltip title={translate("Display extended information for this WebAuthn credential")}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
|
@ -167,7 +167,7 @@ export default function WebauthnDeviceItem(props: Props) {
|
|||
{translate("Info")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={translate("Edit information for this Webauthn credential")}>
|
||||
<Tooltip title={translate("Edit information for this WebAuthn credential")}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
|
@ -177,7 +177,7 @@ export default function WebauthnDeviceItem(props: Props) {
|
|||
{translate("Edit")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={translate("Remove this Webauthn credential")}>
|
||||
<Tooltip title={translate("Remove this WebAuthn credential")}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
|
@ -21,10 +21,10 @@ import { PublicKeyCredentialCreationOptionsJSON } from "@simplewebauthn/typescri
|
|||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import InformationIcon from "@components/InformationIcon";
|
||||
import WebauthnRegisterIcon from "@components/WebauthnRegisterIcon";
|
||||
import WebAuthnRegisterIcon from "@components/WebAuthnRegisterIcon";
|
||||
import { useNotifications } from "@hooks/NotificationsContext";
|
||||
import { AttestationResult, AttestationResultFailureString, WebauthnTouchState } from "@models/Webauthn";
|
||||
import { finishRegistration, getAttestationCreationOptions, startWebauthnRegistration } from "@services/Webauthn";
|
||||
import { AttestationResult, AttestationResultFailureString, WebAuthnTouchState } from "@models/WebAuthn";
|
||||
import { finishRegistration, getAttestationCreationOptions, startWebAuthnRegistration } from "@services/WebAuthn";
|
||||
|
||||
const steps = ["Description", "Verification"];
|
||||
|
||||
|
@ -34,13 +34,13 @@ interface Props {
|
|||
setCancelled: () => void;
|
||||
}
|
||||
|
||||
const WebauthnDeviceRegisterDialog = function (props: Props) {
|
||||
const WebAuthnDeviceRegisterDialog = function (props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
const styles = useStyles();
|
||||
const { createErrorNotification } = useNotifications();
|
||||
|
||||
const [state, setState] = useState(WebauthnTouchState.WaitTouch);
|
||||
const [state, setState] = useState(WebAuthnTouchState.WaitTouch);
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [options, setOptions] = useState<PublicKeyCredentialCreationOptionsJSON | null>(null);
|
||||
const [timeout, setTimeout] = useState<number | null>(null);
|
||||
|
@ -50,7 +50,7 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
const nameRef = useRef() as MutableRefObject<HTMLInputElement>;
|
||||
|
||||
const resetStates = () => {
|
||||
setState(WebauthnTouchState.WaitTouch);
|
||||
setState(WebAuthnTouchState.WaitTouch);
|
||||
setOptions(null);
|
||||
setActiveStep(0);
|
||||
setTimeout(null);
|
||||
|
@ -73,9 +73,9 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
setActiveStep(1);
|
||||
|
||||
try {
|
||||
setState(WebauthnTouchState.WaitTouch);
|
||||
setState(WebAuthnTouchState.WaitTouch);
|
||||
|
||||
const resultCredentialCreation = await startWebauthnRegistration(options);
|
||||
const resultCredentialCreation = await startWebAuthnRegistration(options);
|
||||
|
||||
setTimeout(null);
|
||||
|
||||
|
@ -99,7 +99,7 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
}
|
||||
|
||||
createErrorNotification(AttestationResultFailureString(resultCredentialCreation.result));
|
||||
setState(WebauthnTouchState.Failure);
|
||||
setState(WebAuthnTouchState.Failure);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createErrorNotification(
|
||||
|
@ -109,7 +109,7 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
}, [options, createErrorNotification, handleClose]);
|
||||
|
||||
useEffect(() => {
|
||||
if (state !== WebauthnTouchState.Failure || activeStep !== 0 || !props.open) {
|
||||
if (state !== WebAuthnTouchState.Failure || activeStep !== 0 || !props.open) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -151,12 +151,12 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
break;
|
||||
case 409:
|
||||
setErrorDescription(true);
|
||||
createErrorNotification(translate("A Webauthn Credential with that Description already exists."));
|
||||
createErrorNotification(translate("A WebAuthn Credential with that Description already exists."));
|
||||
|
||||
break;
|
||||
default:
|
||||
createErrorNotification(
|
||||
translate("Error occurred obtaining the Webauthn Credential creation options."),
|
||||
translate("Error occurred obtaining the WebAuthn Credential creation options."),
|
||||
);
|
||||
}
|
||||
}, [createErrorNotification, credentialDescription, translate]);
|
||||
|
@ -214,7 +214,7 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
return (
|
||||
<Fragment>
|
||||
<Box className={styles.icon}>
|
||||
{timeout !== null ? <WebauthnRegisterIcon timeout={timeout} /> : null}
|
||||
{timeout !== null ? <WebAuthnRegisterIcon timeout={timeout} /> : null}
|
||||
</Box>
|
||||
<Typography className={styles.instruction}>
|
||||
{translate("Touch the token on your security key")}
|
||||
|
@ -234,11 +234,11 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
|
||||
return (
|
||||
<Dialog open={props.open} onClose={handleOnClose} maxWidth={"xs"} fullWidth={true}>
|
||||
<DialogTitle>{translate("Register Webauthn Credential")}</DialogTitle>
|
||||
<DialogTitle>{translate("Register WebAuthn Credential")}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText sx={{ mb: 3 }}>
|
||||
{translate(
|
||||
"This page allows registration of a new Security Key backed by modern Webauthn Credential technology.",
|
||||
"This page allows registration of a new Security Key backed by modern WebAuthn Credential technology.",
|
||||
)}
|
||||
</DialogContentText>
|
||||
<Grid container spacing={0} alignItems={"center"} justifyContent={"center"} textAlign={"center"}>
|
||||
|
@ -264,8 +264,8 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
color={activeStep === 1 && state !== WebauthnTouchState.Failure ? "primary" : "error"}
|
||||
disabled={activeStep === 1 && state !== WebauthnTouchState.Failure}
|
||||
color={activeStep === 1 && state !== WebAuthnTouchState.Failure ? "primary" : "error"}
|
||||
disabled={activeStep === 1 && state !== WebAuthnTouchState.Failure}
|
||||
onClick={handleClose}
|
||||
>
|
||||
{translate("Cancel")}
|
||||
|
@ -286,7 +286,7 @@ const WebauthnDeviceRegisterDialog = function (props: Props) {
|
|||
);
|
||||
};
|
||||
|
||||
export default WebauthnDeviceRegisterDialog;
|
||||
export default WebAuthnDeviceRegisterDialog;
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
icon: {
|
|
@ -5,17 +5,17 @@ import { useTranslation } from "react-i18next";
|
|||
|
||||
import { AutheliaState } from "@services/State";
|
||||
import LoadingPage from "@views/LoadingPage/LoadingPage";
|
||||
import WebauthnDeviceRegisterDialog from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceRegisterDialog";
|
||||
import WebauthnDevicesStack from "@views/Settings/TwoFactorAuthentication/WebauthnDevicesStack";
|
||||
import WebAuthnDeviceRegisterDialog from "@views/Settings/TwoFactorAuthentication/WebAuthnDeviceRegisterDialog";
|
||||
import WebAuthnDevicesStack from "@views/Settings/TwoFactorAuthentication/WebAuthnDevicesStack";
|
||||
|
||||
interface Props {
|
||||
state: AutheliaState;
|
||||
}
|
||||
|
||||
export default function WebauthnDevices(props: Props) {
|
||||
export default function WebAuthnDevices(props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
const [showWebauthnDeviceRegisterDialog, setShowWebauthnDeviceRegisterDialog] = useState<boolean>(false);
|
||||
const [showWebAuthnDeviceRegisterDialog, setShowWebAuthnDeviceRegisterDialog] = useState<boolean>(false);
|
||||
const [refreshState, setRefreshState] = useState<number>(0);
|
||||
|
||||
const handleIncrementRefreshState = () => {
|
||||
|
@ -24,13 +24,13 @@ export default function WebauthnDevices(props: Props) {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<WebauthnDeviceRegisterDialog
|
||||
open={showWebauthnDeviceRegisterDialog}
|
||||
<WebAuthnDeviceRegisterDialog
|
||||
open={showWebAuthnDeviceRegisterDialog}
|
||||
onClose={() => {
|
||||
handleIncrementRefreshState();
|
||||
}}
|
||||
setCancelled={() => {
|
||||
setShowWebauthnDeviceRegisterDialog(false);
|
||||
setShowWebAuthnDeviceRegisterDialog(false);
|
||||
handleIncrementRefreshState();
|
||||
}}
|
||||
/>
|
||||
|
@ -38,15 +38,15 @@ export default function WebauthnDevices(props: Props) {
|
|||
<Box sx={{ p: 3 }}>
|
||||
<Stack spacing={2}>
|
||||
<Box>
|
||||
<Typography variant="h5">{translate("Webauthn Credentials")}</Typography>
|
||||
<Typography variant="h5">{translate("WebAuthn Credentials")}</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Tooltip title={translate("Click to add a Webauthn credential to your account")}>
|
||||
<Tooltip title={translate("Click to add a WebAuthn credential to your account")}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setShowWebauthnDeviceRegisterDialog(true);
|
||||
setShowWebAuthnDeviceRegisterDialog(true);
|
||||
}}
|
||||
>
|
||||
{translate("Add Credential")}
|
||||
|
@ -54,7 +54,7 @@ export default function WebauthnDevices(props: Props) {
|
|||
</Tooltip>
|
||||
</Box>
|
||||
<Suspense fallback={<LoadingPage />}>
|
||||
<WebauthnDevicesStack
|
||||
<WebAuthnDevicesStack
|
||||
refreshState={refreshState}
|
||||
incrementRefreshState={handleIncrementRefreshState}
|
||||
/>
|
|
@ -3,24 +3,24 @@ import React, { Fragment, useEffect, useState } from "react";
|
|||
import { Stack, Typography } from "@mui/material";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { WebauthnDevice } from "@models/Webauthn";
|
||||
import { getWebauthnDevices } from "@services/UserWebauthnDevices";
|
||||
import WebauthnDeviceItem from "@views/Settings/TwoFactorAuthentication/WebauthnDeviceItem";
|
||||
import { WebAuthnDevice } from "@models/WebAuthn";
|
||||
import { getWebAuthnDevices } from "@services/UserWebAuthnDevices";
|
||||
import WebAuthnDeviceItem from "@views/Settings/TwoFactorAuthentication/WebAuthnDeviceItem";
|
||||
|
||||
interface Props {
|
||||
refreshState: number;
|
||||
incrementRefreshState: () => void;
|
||||
}
|
||||
|
||||
export default function WebauthnDevicesStack(props: Props) {
|
||||
export default function WebAuthnDevicesStack(props: Props) {
|
||||
const { t: translate } = useTranslation("settings");
|
||||
|
||||
const [devices, setDevices] = useState<WebauthnDevice[] | null>(null);
|
||||
const [devices, setDevices] = useState<WebAuthnDevice[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
setDevices(null);
|
||||
const devices = await getWebauthnDevices();
|
||||
const devices = await getWebAuthnDevices();
|
||||
setDevices(devices);
|
||||
})();
|
||||
}, [props.refreshState]);
|
||||
|
@ -30,11 +30,11 @@ export default function WebauthnDevicesStack(props: Props) {
|
|||
{devices !== null && devices.length !== 0 ? (
|
||||
<Stack spacing={3}>
|
||||
{devices.map((x, idx) => (
|
||||
<WebauthnDeviceItem key={idx} index={idx} device={x} handleEdit={props.incrementRefreshState} />
|
||||
<WebAuthnDeviceItem key={idx} index={idx} device={x} handleEdit={props.incrementRefreshState} />
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<Typography>{translate("No Registered Webauthn Credentials")}</Typography>
|
||||
<Typography>{translate("No Registered WebAuthn Credentials")}</Typography>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
Loading…
Reference in New Issue