// SPDX-License-Identifier: AGPL-3.0-or-later import {MFA_CODE_DIGIT_COUNT} from '@app/features/app/config/I18nDisplayConstants'; import FormField from '@app/features/auth/flow/AuthFormField'; import styles from '@app/features/auth/flow/MfaScreen.module.css'; import {useAuthCardPresentation} from '@app/features/auth/flow/useAuthCardPresentation'; import {useMfaController} from '@app/features/auth/hooks/useLoginFlow'; import type {LoginSuccessPayload, MfaChallenge} from '@app/features/auth/state/AuthFlow'; import { BACK_TO_SIGN_IN_DESCRIPTOR, SIGN_IN_DESCRIPTOR, TWO_FACTOR_AUTHENTICATION_DESCRIPTOR, } from '@app/features/i18n/utils/CommonMessageDescriptors'; import {Button} from '@app/features/ui/button/Button'; import {msg} from '@lingui/core/macro'; import {useLingui} from '@lingui/react/macro'; const CODE_DESCRIPTOR = msg({ message: 'Authenticator code', comment: 'Label for the MFA login one-time password field from an authenticator app or backup code.', }); const AUTHENTICATION_CODE_PLACEHOLDER_DESCRIPTOR = msg({ message: '6-digit code', comment: 'Placeholder for the MFA login one-time password field.', }); const SECURITY_KEY_OR_PASSKEY_DESCRIPTOR = msg({ message: 'Security key / passkey', comment: 'MFA action button for authenticating with a hardware security key or passkey.', }); const MFA_CODE_INSTRUCTIONS_DESCRIPTOR = msg({ message: 'Enter the {digitCount}-digit code from your authenticator app or a backup code.', comment: 'MFA code entry instructions. digitCount is the expected authenticator-code length.', }); const TRY_SECURITY_KEY_INSTEAD_DESCRIPTOR = msg({ message: 'Try security key / passkey instead', comment: 'Secondary MFA action that switches from code entry to passkey or security-key authentication.', }); interface MfaScreenProps { challenge: MfaChallenge; inviteCode?: string; onSuccess: (payload: LoginSuccessPayload) => Promise | void; onCancel: () => void; } const MfaScreen = ({challenge, inviteCode, onSuccess, onCancel}: MfaScreenProps) => { const {i18n} = useLingui(); const {form, isLoading, fieldErrors, handleWebAuthn, isWebAuthnLoading, supports} = useMfaController({ ticket: challenge.ticket, methods: {totp: challenge.totp, webauthn: challenge.webauthn}, inviteCode, onLoginSuccess: onSuccess, }); useAuthCardPresentation({showLogoSide: false, variant: 'compact'}); const showCodeForm = supports.totp; const showWebAuthn = supports.webauthn; return (

{i18n._(TWO_FACTOR_AUTHENTICATION_DESCRIPTOR)}

{showCodeForm && (

{i18n._(MFA_CODE_INSTRUCTIONS_DESCRIPTOR, {digitCount: MFA_CODE_DIGIT_COUNT})}

)} {showCodeForm && (
form.setValue('code', value)} error={form.getError('code') || fieldErrors?.get('code')} data-flx="auth.flow.mfa-screen.form-field.set-value.text" /> )} {showWebAuthn && (
)}
); }; export default MfaScreen;