From bbe10b1618fc6bbbee9cb44d64b592d50bce822a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 08:21:55 +0530 Subject: [PATCH] Update --- web/packages/accounts/pages/login.tsx | 20 ++--- .../accounts/pages/passkeys/finish.tsx | 7 +- web/packages/accounts/pages/recover.tsx | 89 ++++++++++--------- .../accounts/pages/two-factor/recover.tsx | 36 ++++---- .../accounts/pages/two-factor/verify.tsx | 2 + 5 files changed, 85 insertions(+), 69 deletions(-) diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index 6d36a1cd84..9aab2d2105 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -110,12 +110,12 @@ import React, { useCallback, useEffect, useState } from "react"; * - Redirects to "/" if there is no `email` or `twoFactorSessionID` in the * saved partial local user. * - * - Redirects to "/credentials" if there `isTwoFactorEnabled` is not `true` - * and either of `encryptedToken` or `token` is present in the saved partial + * - Redirects to "/credentials" if `isTwoFactorEnabled` is not `true` and + * either of `encryptedToken` or `token` is present in the saved partial * local user. * - * - "/passkeys/finish" - A page that the accounts app hands off control back to - * us (the calling app) to continue the rest of the authentication. + * - "/passkeys/finish" - A page where the accounts app hands off control back + * to us (the calling app) once the passkey has been verified. * * - Redirects to "/" if there is no matching `inflightPasskeySessionID` in * session storage. @@ -124,15 +124,15 @@ import React, { useCallback, useEffect, useState } from "react"; * * - "/two-factor/recover" and "/passkeys/recover" - Pages that allow the user * to reset or bypass their second factor if they possess their recovery key. - * Both pages work similarly, except the second factor they act on. + * Both pages work similarly, except for the second factor they act on. * - * - Redirects to "/" if there is no `email` in the saved partial local user, - * or either of `twoFactorSessionID` and `twoFactorSessionID` is set. + * - Redirect to "/" if there is no `email` or `twoFactorSessionID` / + * `passkeySessionID` in the saved partial local user. * - * - Redirects to "/generate" if there is an `encryptedToken` or `token` in - * the saved partial local user. + * - Redirect to "/generate" if there is an `encryptedToken` or `token` in the + * saved partial local user. * - * - Redirects to "/credentials" after recovery. + * - Redirect to "/credentials" after recovery. * */ const Page: React.FC = () => { diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index 1518a61b94..b8fd76e89c 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -13,6 +13,11 @@ import { useRouter } from "next/router"; import React, { useEffect } from "react"; /** + * The page where the accounts app hands back control to us once the passkey has + * been verified. + * + * See: [Note: Login pages] + * * [Note: Finish passkey flow in the requesting app] * * The passkey finish step needs to happen in the context of the client which @@ -23,7 +28,7 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - // Extract response from query params + // Extract response from query params. const searchParams = new URLSearchParams(window.location.search); const passkeySessionID = searchParams.get("passkeySessionID"); const response = searchParams.get("response"); diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index edbe1e9e6a..23c789d475 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -26,7 +26,7 @@ import { } from "ente-base/session"; import { t } from "i18next"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; /** * A page that allows the user to enter their recovery key to recover their @@ -44,58 +44,63 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user = savedPartialLocalUser(); - if (!user?.email) { - void router.push("/"); - return; - } - if (!user?.encryptedToken && !user?.token) { - void sendOTT(user.email, undefined); - stashRedirect("/recover"); - void router.push("/verify"); - return; - } + void (async () => { + const user = savedPartialLocalUser(); + if (!user?.email) { + await router.push("/"); + return; + } - const keyAttributes = savedKeyAttributes(); - if (!keyAttributes) { - void router.push("/generate"); - } else if (haveMasterKeyInSession()) { - void router.push(appHomeRoute); - } else { - setKeyAttributes(keyAttributes); - } + if (!user.encryptedToken && !user.token) { + await sendOTT(user.email, undefined); + stashRedirect("/recover"); + await router.push("/verify"); + return; + } + + const keyAttributes = savedKeyAttributes(); + if (!keyAttributes) { + await router.push("/generate"); + } else if (haveMasterKeyInSession()) { + await router.push(appHomeRoute); + } else { + setKeyAttributes(keyAttributes); + } + })(); }, [router]); - const handleSubmit: SingleInputFormProps["onSubmit"] = async ( - recoveryKeyMnemonic: string, - setFieldError, - ) => { - try { - const keyAttr = keyAttributes!; - const masterKey = await decryptBox( - { - encryptedData: keyAttr.masterKeyEncryptedWithRecoveryKey!, - nonce: keyAttr.masterKeyDecryptionNonce!, - }, - await recoveryKeyFromMnemonic(recoveryKeyMnemonic), - ); - await saveMasterKeyInSessionAndSafeStore(masterKey); - await decryptAndStoreToken(keyAttr, masterKey); + const handleSubmit: SingleInputFormProps["onSubmit"] = useCallback( + async (recoveryKeyMnemonic: string, setFieldError) => { + try { + const keyAttr = keyAttributes!; + const masterKey = await decryptBox( + { + encryptedData: + keyAttr.masterKeyEncryptedWithRecoveryKey!, + nonce: keyAttr.masterKeyDecryptionNonce!, + }, + await recoveryKeyFromMnemonic(recoveryKeyMnemonic), + ); + await saveMasterKeyInSessionAndSafeStore(masterKey); + await decryptAndStoreToken(keyAttr, masterKey); - void router.push("/change-password?op=reset"); - } catch (e) { - log.error("password recovery failed", e); - setFieldError(t("incorrect_recovery_key")); - } - }; + void router.push("/change-password?op=reset"); + } catch (e) { + log.error("Master key recovery failed", e); + setFieldError(t("incorrect_recovery_key")); + } + }, + [router, keyAttributes], + ); - const showNoRecoveryKeyMessage = () => + const showNoRecoveryKeyMessage = useCallback(() => { showMiniDialog({ title: t("sorry"), message: t("no_recovery_key_message"), continue: { color: "secondary" }, cancel: false, }); + }, [showMiniDialog]); return ( diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index caa3480661..6b17dfec6d 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -64,20 +64,23 @@ const Page: React.FC = ({ twoFactorType }) => { ); useEffect(() => { - const user = savedPartialLocalUser(); - const sessionID = - twoFactorType == "passkey" - ? user?.passkeySessionID - : user?.twoFactorSessionID; - if (!user?.email || !sessionID) { - void router.push("/"); - } else if (user.encryptedToken || user.token) { - void router.push("/generate"); - } else { - setSessionID(sessionID); - void recoverTwoFactor(twoFactorType, sessionID) - .then(setRecoveryResponse) - .catch((e: unknown) => { + void (async () => { + const user = savedPartialLocalUser(); + const sessionID = + twoFactorType == "passkey" + ? user?.passkeySessionID + : user?.twoFactorSessionID; + if (!user?.email || !sessionID) { + await router.push("/"); + } else if (user.encryptedToken || user.token) { + await router.push("/generate"); + } else { + setSessionID(sessionID); + try { + setRecoveryResponse( + await recoverTwoFactor(twoFactorType, sessionID), + ); + } catch (e) { log.error("Second factor recovery page setup failed", e); if (isHTTPErrorWithStatus(e, 404)) { logout(); @@ -86,8 +89,9 @@ const Page: React.FC = ({ twoFactorType }) => { } else { onGenericError(e); } - }); - } + } + } + })(); }, [ twoFactorType, logout, diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index 918fba2486..c11a1238a7 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -21,6 +21,8 @@ import { unstashRedirect } from "../../services/redirect"; /** * A page that allows the user to verify their TOTP based second factor. + * + * See: [Note: Login pages] */ const Page: React.FC = () => { const { logout } = useBaseContext();