From 7029255b91e6d82efd7166e906652ec6d36a9d55 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Mon, 30 Oct 2023 10:53:56 +0800 Subject: [PATCH] refactor(experience): polish handle mfa redirection codes and add comments (#4782) --- .../src/hooks/use-mfa-error-handler.ts | 55 +++++++++++++------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/packages/experience/src/hooks/use-mfa-error-handler.ts b/packages/experience/src/hooks/use-mfa-error-handler.ts index 702a758ed..a4fa67d21 100644 --- a/packages/experience/src/hooks/use-mfa-error-handler.ts +++ b/packages/experience/src/hooks/use-mfa-error-handler.ts @@ -1,5 +1,6 @@ import { MfaFactor, type RequestErrorBody } from '@logto/schemas'; import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; import { validate } from 'superstruct'; @@ -22,45 +23,67 @@ export type Options = { const useMfaErrorHandler = ({ replace }: Options = {}) => { const navigate = useNavigate(); + const { t } = useTranslation(); const { setToast } = useToast(); const startTotpBinding = useStartTotpBinding({ replace }); + /** + * Redirect the user to the corresponding MFA page. + * + * Binding pages are hosted on following routes: + * - /{@link UserMfaFlow.MfaBinding} List of available MFA factors for binding. + * - /{@link UserMfaFlow.MfaBinding}/{@link MfaFactor} Binding page for the specific factor. + * + * Verification pages are hosted on following routes: + * - /{@link UserMfaFlow.MfaVerification} List of available MFA factors for verification. + * - /{@link UserMfaFlow.MfaVerification}/{@link MfaFactor} Verification page for the specific factor. + * + * Redirection rules: + * - If there is only one available factor, redirect to the specific MFA factor page. + * - If there are multiple available factors: + * - Binding: redirect to the available factors list page. + * - Verification: redirect to the last used specific factor page. + */ const handleMfaRedirect = useCallback( (flow: UserMfaFlow, state: MfaFlowState) => { const { availableFactors } = state; - if (flow === UserMfaFlow.MfaVerification) { - // In MFA verification flow, the user will be redirected to the last used factor which is the first one in the array. - const factor = availableFactors[0]; - - if (!factor) { - return; - } - - navigate({ pathname: `/${flow}/${factor}` }, { replace, state }); - return; - } - - // Binding flow - if (availableFactors.length > 1) { + if (availableFactors.length > 1 && flow === UserMfaFlow.MfaBinding) { + /** + * Redirect to the MFA binding page if there are multiple available factors. + */ navigate({ pathname: `/${flow}` }, { replace, state }); return; } + /** + * For verification: the first available factor is the last used factor which is guaranteed by the backend. + * For binding: the first available factor is the only available factor since we handle the multiple factors case above. + */ const factor = availableFactors[0]; if (!factor) { + /** + * This should never happen since we check the available factors' length before handling the redirection. + */ + setToast(t('error.unknown')); return; } - if (factor === MfaFactor.TOTP) { + if (factor === MfaFactor.TOTP && flow === UserMfaFlow.MfaBinding) { + /** + * Start TOTP binding process if only TOTP is available. + */ void startTotpBinding(state); return; } + /** + * Redirect to the specific MFA factor page. + */ navigate({ pathname: `/${flow}/${factor}` }, { replace, state }); }, - [navigate, replace, startTotpBinding] + [navigate, replace, setToast, startTotpBinding, t] ); const handleMfaError = useCallback(