Merge orgin/master into feat-settings-ui

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
feat-otp-verification
James Elliott 2023-04-15 03:08:43 +10:00
commit 6c89ee1f9c
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
39 changed files with 156 additions and 156 deletions

View File

@ -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{

View File

@ -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"
}

View File

@ -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);

View File

@ -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

View File

@ -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";

View File

@ -1,5 +1,5 @@
export enum SecondFactorMethod {
TOTP = 1,
Webauthn,
WebAuthn,
MobilePush,
}

View File

@ -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,

View File

@ -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";

View File

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

View File

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

View File

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

View File

@ -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 {

View File

@ -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) ? (

View File

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

View File

@ -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;

View File

@ -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>
);

View File

@ -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>

View File

@ -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>

View File

@ -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}

View File

@ -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"

View File

@ -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: {

View File

@ -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}
/>

View File

@ -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>
);