This commit is contained in:
Manav Rathi
2025-07-04 08:21:55 +05:30
parent c9521fb626
commit bbe10b1618
5 changed files with 85 additions and 69 deletions

View File

@@ -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 = () => {

View File

@@ -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");

View File

@@ -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 (
<AccountsPageContents>

View File

@@ -64,20 +64,23 @@ const Page: React.FC<RecoverPageProps> = ({ 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<RecoverPageProps> = ({ twoFactorType }) => {
} else {
onGenericError(e);
}
});
}
}
}
})();
}, [
twoFactorType,
logout,

View File

@@ -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();