From 6d5bc3ca09d6eea6e10598e00f5fa871127f6583 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 12 Apr 2026 14:40:23 +0530 Subject: [PATCH] fix: update keystore nullish check --- frontend/src/utils/keystore.test.ts | 56 +++++++++++++++++++++++++++++ frontend/src/utils/keystore.ts | 18 ++++++---- frontend/test/setup.ts | 11 +----- 3 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 frontend/src/utils/keystore.test.ts diff --git a/frontend/src/utils/keystore.test.ts b/frontend/src/utils/keystore.test.ts new file mode 100644 index 0000000..1398494 --- /dev/null +++ b/frontend/src/utils/keystore.test.ts @@ -0,0 +1,56 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { CryptoUtils } from "./crypto"; +import { clearMasterKey, loadMasterKey, saveMasterKey } from "./keystore"; + +afterEach(async () => { + // clear to avoid in-memory db conflicts when running tests in parallel + await clearMasterKey(); +}); + +async function makeMasterKey() { + return CryptoUtils.deriveMasterKey("test-password", "test@example.com"); +} + +describe("keystore", () => { + it("should save and load a CryptoKey successfully", async () => { + const key = await makeMasterKey(); + + await saveMasterKey(key); + const keyfromMemory = await loadMasterKey(); + + expect(keyfromMemory).toBeInstanceOf(CryptoKey); + expect(keyfromMemory).toEqual(key); + }); + + it("should remove the stored key from memory", async () => { + await saveMasterKey(await makeMasterKey()); + await clearMasterKey(); + + const keyfromMemory = await loadMasterKey(); + + expect(keyfromMemory).toBeNull(); + }); + + async function generateTestKey() { + // generate a random 'extractable' key for testing + return crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, [ + "encrypt", + "decrypt", + ]); + } + + it("should overwrite the previous key when calling saveMasterKey twice", async () => { + const key1 = await generateTestKey(); + const key2 = await generateTestKey(); + + await saveMasterKey(key1); + await saveMasterKey(key2); + const loadedKey = await loadMasterKey(); + const loadedJwk = await crypto.subtle.exportKey("jwk", loadedKey); + const key1Jwk = await crypto.subtle.exportKey("jwk", key1); + const key2Jwk = await crypto.subtle.exportKey("jwk", key2); + + expect(loadedJwk).toStrictEqual(key2Jwk); + expect(loadedJwk).not.toStrictEqual(key1Jwk); + }); +}); diff --git a/frontend/src/utils/keystore.ts b/frontend/src/utils/keystore.ts index c7e12c7..30b3b46 100644 --- a/frontend/src/utils/keystore.ts +++ b/frontend/src/utils/keystore.ts @@ -7,11 +7,17 @@ const db = openDB("piku-keys", 1, { }, }); -export const saveMasterKey = async (key: CryptoKey) => - (await db).put("master-key", key, "masterKey"); +export const saveMasterKey = async (key: CryptoKey) => { + const database = await db; + return await database.put("master-key", key, "masterKey"); +}; -export const loadMasterKey = async (): Promise => - (await db).get("master-key", "masterKey") ?? null; +export const loadMasterKey = async (): Promise => { + const database = await db; + return (await database.get("master-key", "masterKey")) || null; +}; -export const clearMasterKey = async () => - (await db).delete("master-key", "masterKey"); +export const clearMasterKey = async () => { + const database = await db; + return await database.delete("master-key", "masterKey"); +}; diff --git a/frontend/test/setup.ts b/frontend/test/setup.ts index 78854b9..9c67de4 100644 --- a/frontend/test/setup.ts +++ b/frontend/test/setup.ts @@ -1,17 +1,8 @@ import "@testing-library/jest-dom"; -import { IDBFactory } from "fake-indexeddb"; +import "fake-indexeddb/auto"; // auto configures the indexedDB import { afterAll, afterEach, beforeAll } from "vitest"; import { server } from "./mocks/server"; -/** - * faking indexeddb in memory for testing crypto key storage - */ -Object.defineProperty(globalThis, "indexedDB", { - value: new IDBFactory(), - writable: true, - configurable: true, -}); - beforeAll(() => server.listen({ onUnhandledRequest: "warn" })); afterEach(() => server.resetHandlers()); afterAll(() => server.close());