diff --git a/new-ui/src/pages/full/ConfigureMfaPage/ConfigureMfaPage.tsx b/new-ui/src/pages/full/ConfigureMfaPage/ConfigureMfaPage.tsx index 697870a1a..c567176c9 100644 --- a/new-ui/src/pages/full/ConfigureMfaPage/ConfigureMfaPage.tsx +++ b/new-ui/src/pages/full/ConfigureMfaPage/ConfigureMfaPage.tsx @@ -7,6 +7,7 @@ import { } from './components/ConfigureMfaTimeoutProvider'; import { useConfigureMfaStore } from './hooks/useConfigureMfaStore'; import { ConfigureMfaVerify } from './verify/ConfigureMfaVerify'; +import { ConfigureSelectMethodsStep } from './verify/ConfigureSelectMethodsStep/ConfigureSelectMethodsStep'; import { ConfigureMfaWizard } from './wizard/ConfigureMfaWizard'; export const ConfigureMfaPage = () => { @@ -19,10 +20,8 @@ export const ConfigureMfaPage = () => { const ConfigureMfaContent = () => { const navigate = useNavigate(); - // a late authorization on the selection screen waits for the picks - const inWizard = useConfigureMfaStore( - (s) => s.authorized && isPresent(s.selectedMethods), - ); + const authorized = useConfigureMfaStore((s) => s.authorized); + const methodsSelected = useConfigureMfaStore((s) => isPresent(s.selectedMethods)); const handleSessionExpired = useConfigureMfaSessionExpired(); const leave = useCallback(() => { @@ -31,12 +30,13 @@ const ConfigureMfaContent = () => { return ( <> - {inWizard && ( - - )} - {!inWizard && ( + {!authorized && ( )} + {authorized && !methodsSelected && } + {authorized && methodsSelected && ( + + )} ); }; diff --git a/new-ui/src/pages/full/ConfigureMfaPage/components/ConfigureMfaTimeoutProvider.tsx b/new-ui/src/pages/full/ConfigureMfaPage/components/ConfigureMfaTimeoutProvider.tsx index b2453a9ef..b432004e4 100644 --- a/new-ui/src/pages/full/ConfigureMfaPage/components/ConfigureMfaTimeoutProvider.tsx +++ b/new-ui/src/pages/full/ConfigureMfaPage/components/ConfigureMfaTimeoutProvider.tsx @@ -27,12 +27,10 @@ export const useConfigureMfaSessionExpired = (): (() => void) => { export const ConfigureMfaTimeoutProvider = ({ children }: PropsWithChildren) => { const [expired, setExpired] = useState(false); const deadline = useConfigureMfaStore((s) => s.deadline); - // Picking only the email fallback opens on Finish before verification, hence the auth check. const settled = useConfigureMfaStore( (s) => - s.authorized && - (s.activeStep === ConfigureMfaStep.RecoveryCodes || - s.activeStep === ConfigureMfaStep.Finish), + s.activeStep === ConfigureMfaStep.RecoveryCodes || + s.activeStep === ConfigureMfaStep.Finish, ); const expire = useCallback(() => setExpired(true), []); diff --git a/new-ui/src/pages/full/ConfigureMfaPage/components/ConfigureMfaVerificatorFactorSelector/ConfigureMfaVerificatorFactorSelector.tsx b/new-ui/src/pages/full/ConfigureMfaPage/components/ConfigureMfaVerificatorFactorSelector/ConfigureMfaVerificatorFactorSelector.tsx index 1b44494a4..50076a092 100644 --- a/new-ui/src/pages/full/ConfigureMfaPage/components/ConfigureMfaVerificatorFactorSelector/ConfigureMfaVerificatorFactorSelector.tsx +++ b/new-ui/src/pages/full/ConfigureMfaPage/components/ConfigureMfaVerificatorFactorSelector/ConfigureMfaVerificatorFactorSelector.tsx @@ -4,6 +4,7 @@ import { Icon } from '../../../../../shared/components/Icon'; import { RadioIndicator } from '../../../../../shared/components/RadioIndicator/RadioIndicator'; import { mfaMethodIcon } from '../../../../../shared/consts'; import { MfaMethod, type MfaMethodValue } from '../../../../../shared/rust-api/types'; +import { mfaToText } from '../../../../../shared/utils/mfa'; type FactorText = { title: string; @@ -24,7 +25,7 @@ const factorText = (method: MfaMethodValue): FactorText => { }; case MfaMethod.Oidc: return { - title: 'OpenID sign-in', + title: mfaToText(MfaMethod.Oidc), description: `Sign in with your identity provider in the browser to confirm it's you.`, }; case MfaMethod.Biometric: diff --git a/new-ui/src/pages/full/ConfigureMfaPage/components/MfaCodeVerification/MfaCodeVerification.tsx b/new-ui/src/pages/full/ConfigureMfaPage/components/MfaCodeVerification/MfaCodeVerification.tsx new file mode 100644 index 000000000..62da84631 --- /dev/null +++ b/new-ui/src/pages/full/ConfigureMfaPage/components/MfaCodeVerification/MfaCodeVerification.tsx @@ -0,0 +1,92 @@ +import './style.scss'; +import type { ReactNode } from 'react'; +import { Button } from '../../../../../shared/components/Button/Button'; +import { ButtonVariant } from '../../../../../shared/components/Button/types'; +import { CodeInput } from '../../../../../shared/components/CodeInput/CodeInput'; +import { Controls } from '../../../../../shared/components/Controls/Controls'; +import { Icon, type IconKindValue } from '../../../../../shared/components/Icon'; +import { FullPage } from '../../../../../shared/layouts/FullPage/FullPage'; +import { isPresent } from '../../../../../shared/utils/isPresent'; + +const codeLength = 6; + +interface Props { + id: string; + icon: IconKindValue; + title: string; + description: string[]; + code: string | null; + error: string | null; + onCodeChange: (value: string) => void; + onSubmit: (pastedCode?: string) => void; + leaveText: string; + onLeave: () => void; + extraAction?: ReactNode; + codeLoading?: boolean; + isLeaving?: boolean; + leaveDisabled?: boolean; + submitLoading?: boolean; + submitDisabled?: boolean; +} + +export const MfaCodeVerification = ({ + id, + icon, + title, + description, + code, + error, + onCodeChange, + onSubmit, + leaveText, + onLeave, + extraAction, + codeLoading = false, + isLeaving = false, + leaveDisabled = false, + submitLoading = false, + submitDisabled = false, +}: Props) => { + return ( + +
+ +

{title}

+

+ {description.map((line) => ( + {line} + ))} +

+ +
+ +