diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index 40134e3c7f..3cbfbd776f 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -37,7 +37,7 @@ export interface RecoverPageProps { const Page: React.FC = ({ twoFactorType }) => { const { logout, showMiniDialog, onGenericError } = useBaseContext(); - const [sessionID, setSessionID] = useState(null); + const [sessionID, setSessionID] = useState(undefined); const [recoveryResponse, setRecoveryResponse] = useState< TwoFactorRecoveryResponse | undefined >(undefined); @@ -65,8 +65,8 @@ const Page: React.FC = ({ twoFactorType }) => { useEffect(() => { const user = getData("user"); - const sid = user.passkeySessionID || user.twoFactorSessionID; - if (!user?.email || !sid) { + const sessionID = user.passkeySessionID || user.twoFactorSessionID; + if (!user?.email || !sessionID) { void router.push("/"); } else if ( !(user.isTwoFactorEnabled || user.isTwoFactorEnabledPasskey) && @@ -74,8 +74,8 @@ const Page: React.FC = ({ twoFactorType }) => { ) { void router.push("/generate"); } else { - setSessionID(sid); - void recoverTwoFactor(sid, twoFactorType) + setSessionID(sessionID); + void recoverTwoFactor(twoFactorType, sessionID) .then(setRecoveryResponse) .catch((e: unknown) => { log.error("Second factor recovery page setup failed", e); @@ -99,20 +99,18 @@ const Page: React.FC = ({ twoFactorType }) => { const handleSubmit: SingleInputFormProps["onSubmit"] | undefined = useMemo( () => sessionID && recoveryResponse - ? async (recoveryKeyMnemonic: string, setFieldError) => { - try { - await recoverTwoFactorFinish( - sessionID, - twoFactorType, - recoveryResponse, - recoveryKeyMnemonic, - ); - void router.push("/credentials"); - } catch (e) { - log.error("Second factor recovery failed", e); - setFieldError(t("incorrect_recovery_key")); - } - } + ? (recoveryKeyMnemonic, setFieldError) => + recoverTwoFactorFinish( + twoFactorType, + sessionID, + recoveryResponse, + recoveryKeyMnemonic, + ) + .then(() => router.push("/credentials")) + .catch((e: unknown) => { + log.error("Second factor recovery failed", e); + setFieldError(t("incorrect_recovery_key")); + }) : undefined, [twoFactorType, router, sessionID, recoveryResponse], ); diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index a042b3d562..5d356a05bd 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -623,11 +623,11 @@ export type TwoFactorRecoveryResponse = z.infer< * factor recovery secret (and nonce) from remote. The user can then decrypt * these using their recovery key to reset or bypass their second factor. * + * @param twoFactorType The type of second factor to reset or bypass. + * * @param sessionID A two factor session ID ({@link twoFactorSessionID} or * {@link passkeySessionID}) for the user. * - * @param twoFactorType The type of second factor to reset or bypass. - * * [Note: Second factor recovery] * * 1. When setting up a TOTP based second factor, client sends a (encrypted 2fa @@ -648,11 +648,11 @@ export type TwoFactorRecoveryResponse = z.infer< * (passkey based) the user's second factor. */ export const recoverTwoFactor = async ( - sessionID: string, twoFactorType: TwoFactorType, + sessionID: string, ): Promise => { const res = await fetch( - await apiURL("/users/two-factor/recover", { sessionID, twoFactorType }), + await apiURL("/users/two-factor/recover", { twoFactorType, sessionID }), { headers: publicRequestHeaders() }, ); ensureOk(res); @@ -668,13 +668,13 @@ export const recoverTwoFactor = async ( * * This completes the recovery process both locally, and on remote. * + * @param twoFactorType The second factor type (same value as what would've been + * passed to {@link recoverTwoFactor} for obtaining {@link recoveryResponse}). + * * @param sessionID The second factor session ID (same value as what would've * been passed to {@link recoverTwoFactor} for obtaining * {@link recoveryResponse}). * - * @param twoFactorType The second factor type (same value as what would've been - * passed to {@link recoverTwoFactor} for obtaining {@link recoveryResponse}). - * * @param recoveryResponse The response to a previous call to * {@link recoverTwoFactor}. * @@ -682,8 +682,8 @@ export const recoverTwoFactor = async ( * by the user to complete recovery. */ export const recoverTwoFactorFinish = async ( - sessionID: string, twoFactorType: TwoFactorType, + sessionID: string, recoveryResponse: TwoFactorRecoveryResponse, recoveryKeyMnemonic: string, ) => { @@ -694,8 +694,8 @@ export const recoverTwoFactorFinish = async ( await recoveryKeyFromMnemonic(recoveryKeyMnemonic), ); const { keyAttributes, encryptedToken, token, id } = await removeTwoFactor( - sessionID, twoFactorType, + sessionID, twoFactorSecret, ); await setLSUser({ @@ -716,13 +716,13 @@ export interface TwoFactorVerificationResponse { } export const removeTwoFactor = async ( - sessionID: string, twoFactorType: TwoFactorType, + sessionID: string, secret: string, ) => { const resp = await HTTPService.post( await apiURL("/users/two-factor/remove"), - { sessionID, twoFactorType, secret }, + { twoFactorType, sessionID, secret }, ); return resp.data as TwoFactorVerificationResponse; };