From ee5acf6a2e86b44524b7ed2a7fd83c0107e37cb2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 14 Aug 2024 13:46:27 +0530 Subject: [PATCH] Sketch --- web/packages/base/session-store.ts | 4 +- .../new/photos/services/user-entity.ts | 83 +++++++++++++++---- 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/web/packages/base/session-store.ts b/web/packages/base/session-store.ts index 87a044fa56..6e37bac534 100644 --- a/web/packages/base/session-store.ts +++ b/web/packages/base/session-store.ts @@ -2,11 +2,11 @@ import { sharedCryptoWorker } from "@/base/crypto"; import { z } from "zod"; /** - * Return the user's encryption key from session storage. + * Return the base64 encoded user's encryption key from session storage. * * Precondition: The user should be logged in. */ -export const usersEncryptionKey = async () => { +export const usersEncryptionKeyB64 = async () => { // TODO: Same value as the deprecated SESSION_KEYS.ENCRYPTION_KEY. const value = sessionStorage.getItem("encryptionKey"); if (!value) { diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index d3468f317a..8aa12b1810 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -1,9 +1,9 @@ import { sharedCryptoWorker } from "@/base/crypto"; import { decryptAssociatedB64Data } from "@/base/crypto/ente"; -import { authenticatedRequestHeaders, ensureOk } from "@/base/http"; +import { authenticatedRequestHeaders, ensureOk, HTTPError } from "@/base/http"; import { getKV, getKVN, setKV } from "@/base/kv"; import { apiURL } from "@/base/origins"; -import { usersEncryptionKey } from "@/base/session-store"; +import { usersEncryptionKeyB64 } from "@/base/session-store"; import { nullToUndefined } from "@/utils/transform"; import { z } from "zod"; import type { Person } from "./ml/cluster-new"; @@ -165,19 +165,42 @@ export const userEntityDiff = async ( * it locally for future use. * * 3. Otherwise we'll create a new one, save it locally and put it to remote. + * + * See also, [Note: User entity keys]. */ const entityKey = async (type: EntityType) => { - const encryptionKey = await usersEncryptionKey(); + const encryptionKeyB64 = await usersEncryptionKeyB64(); const worker = await sharedCryptoWorker(); + + const decrypt = async ({ encryptedKey, header }: RemoteUserEntityKey) => { + return worker.decryptB64(encryptedKey, header, encryptionKeyB64); + }; + + // See if we already have it locally. const saved = await savedRemoteUserEntityKey(type); - if (saved) { - return worker.decryptB64( - saved.encryptedKey, - saved.header, - encryptionKey, - ); + if (saved) return decrypt(saved); + + // See if remote already has it. + const existing = await getUserEntityKey(type); + if (existing) { + // Only save it if we can decrypt it to avoid corrupting our local state + // in unforeseen circumstances. + const result = decrypt(existing); + await saveRemoteUserEntityKey(type, existing); + return result; } - return undefined; + + // Nada. Create a new one, put it to remote, save it locally, and return. + // TODO-Cluster Keep this read only, only add the writeable bits after other + // stuff has been tested. + throw new Error("Not implemented"); + // const generatedKeyB64 = await worker.generateEncryptionKey(); + // const encryptedNewKey = await worker.encryptToB64( + // generatedKeyB64, + // encryptionKeyB64, + // ); + // await postUserEntityKey(type, newKey); + // return decrypt(newKey); }; const entityKeyKey = (type: EntityType) => `entityKey/${type}`; @@ -202,19 +225,30 @@ const saveRemoteUserEntityKey = ( ) => setKV(entityKeyKey(type), JSON.stringify(entityKey)); /** - * Fetch the latest encryption key for the given user entity {@link} type from - * remote. + * Fetch the encryption key for the given user entity {@link type} from remote. + * + * [Note: User entity keys] + * + * There is one encryption key (itself encrypted with the user's encryption key) + * for each user entity type. If the key doesn't exist on remote, then the + * client is expected to create one on the user's behalf. Remote will disallow + * attempts to multiple keys for the same user entity type. */ const getUserEntityKey = async ( type: EntityType, -): Promise => { +): Promise => { const params = new URLSearchParams({ type }); const url = await apiURL("/user-entity/key"); const res = await fetch(`${url}?${params.toString()}`, { headers: await authenticatedRequestHeaders(), }); - ensureOk(res); - return RemoteUserEntityKey.parse(await res.json()); + if (!res.ok) { + // Remote says HTTP 404 Not Found if there is no key yet for the user. + if (res.status == 404) return undefined; + throw new HTTPError(res); + } else { + return RemoteUserEntityKey.parse(await res.json()); + } }; const RemoteUserEntityKey = z.object({ @@ -224,6 +258,25 @@ const RemoteUserEntityKey = z.object({ type RemoteUserEntityKey = z.infer; +/** + * Create a new encryption key for the given user entity {@link type} on remote. + * + * See: [Note: User entity keys] + */ +// TODO-Cluster remove export +export const postUserEntityKey = async ( + type: EntityType, + entityKey: RemoteUserEntityKey, +) => { + const url = await apiURL("/user-entity/key"); + const res = await fetch(url, { + method: "POST", + headers: await authenticatedRequestHeaders(), + body: JSON.stringify({ type, ...entityKey }), + }); + ensureOk(res); +}; + const latestUpdatedAtKey = (type: EntityType) => `latestUpdatedAt/${type}`; /**