diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 795f4bf8d3..7488dc5f53 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -32,7 +32,10 @@ import { useIsSmallWidth } from "ente-base/components/utils/hooks"; import { useModalVisibility } from "ente-base/components/utils/modal"; import { useBaseContext } from "ente-base/context"; import log from "ente-base/log"; -import { clearSessionStorage, haveCredentialsInSession } from "ente-base/session"; +import { + clearSessionStorage, + haveCredentialsInSession, +} from "ente-base/session"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; import { type Collection } from "ente-media/collection"; import { type EnteFile } from "ente-media/file"; @@ -556,7 +559,6 @@ const Page: React.FC = () => { async (force = false, silent = false) => { if (!navigator.onLine) return; if (!haveCredentialsInSession()) { - // TODO: clear shouldn't be required once we remove kek from ss. clearSessionStorage(); router.push("/credentials"); return; diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index e28e846556..5b523b64a3 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -5,15 +5,19 @@ import { toB64 } from "./crypto/libsodium"; /** * Remove all data stored in session storage (data tied to the browser tab). * - * See `docs/storage.md` for more details about session storage. + * See `docs/storage.md` for more details about session storage. Currently, only + * the following entries are stored in session storage: + * + * - "encryptionKey" + * - "keyEncryptionKey" (transient) */ export const clearSessionStorage = () => sessionStorage.clear(); -// TODO: Same as B64EncryptionResult. Revisit. /** - * Schema of "encryptionKey" JSON string stored in session storage. + * Schema of JSON string value for the "encryptionKey" and "keyEncryptionKey" + * keys strings stored in session storage. */ -const EncryptionKeyAttributes = z.object({ +const SessionKeyData = z.object({ encryptedData: z.string(), key: z.string(), nonce: z.string(), @@ -74,7 +78,7 @@ export const masterKeyFromSessionIfLoggedIn = async () => { const value = sessionStorage.getItem("encryptionKey"); if (!value) return undefined; - const { encryptedData, key, nonce } = EncryptionKeyAttributes.parse( + const { encryptedData, key, nonce } = SessionKeyData.parse( JSON.parse(value), ); return decryptBox({ encryptedData, nonce }, key); @@ -85,3 +89,32 @@ export const masterKeyFromSessionIfLoggedIn = async () => { * base64 string. */ export const masterKeyB64FromSession = () => masterKeyFromSession().then(toB64); + +/** + * Return the decrypted user's key encryption key ("kek") from session storage + * if present, otherwise return `undefined`. + * + * [Note: Stashing kek in session store] + * + * During login, if the user has set a second factor (passkey or TOTP), then we + * need to redirect them to the accounts app or TOTP page to verify the second + * factor. This second factor verification happens after password verification, + * but simply storing the decrypted kek in-memory wouldn't work because the + * second factor redirect can happen to a separate accounts app altogether. + * + * So instead, we stash the encrypted kek in session store (using + * {@link stashKeyEncryptionKeyInSessionStore}), and after redirect, retrieve + * it (after clearing it) using {@link unstashKeyEncryptionKeyFromSession}. + */ +export const unstashKeyEncryptionKeyFromSession = async () => { + // TODO: Same value as the deprecated getKey("keyEncryptionKey") + const value = sessionStorage.getItem("keyEncryptionKey"); + if (!value) return undefined; + + sessionStorage.removeItem("keyEncryptionKey"); + + const { encryptedData, key, nonce } = SessionKeyData.parse( + JSON.parse(value), + ); + return decryptBox({ encryptedData, nonce }, key); +};