import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { DESKTOP_SERVER_URL_KEY } from "./desktopServerConfig"; import { clearSessionToken, getSessionToken, initializeSecureSessionStorage, LEGACY_TOKEN_KEY, resetSecureSessionStorageForTests, setSessionToken, } from "./secureSessionStorage"; const invokeMock = vi.hoisted(() => vi.fn()); vi.mock("@tauri-apps/api/core", () => ({ invoke: invokeMock, })); const SERVER_ORIGIN = "https://ctms.example.com/"; const DESKTOP_SESSION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000; const encodeJson = (value: unknown): string => Buffer.from(JSON.stringify(value)).toString("base64url"); const createJwt = (expiresAtMs: number): string => `${encodeJson({ alg: "none", typ: "JWT" })}.${encodeJson({ exp: Math.floor(expiresAtMs / 1000) })}.signature`; const createStorage = (): Storage => { const data = new Map(); return { get length() { return data.size; }, clear: () => data.clear(), getItem: (key) => data.get(key) ?? null, key: (index) => Array.from(data.keys())[index] ?? null, removeItem: (key) => data.delete(key), setItem: (key, value) => { data.set(key, String(value)); }, }; }; describe("secure session storage", () => { beforeEach(() => { vi.stubEnv("VITE_DESKTOP_SERVER_URL", ""); vi.useFakeTimers(); vi.setSystemTime(new Date("2026-07-02T00:00:00.000Z")); resetSecureSessionStorageForTests(); Object.defineProperty(window, "localStorage", { value: createStorage(), configurable: true }); localStorage.clear(); localStorage.setItem(DESKTOP_SERVER_URL_KEY, SERVER_ORIGIN); Object.defineProperty(window, "isTauri", { value: true, configurable: true }); invokeMock.mockReset(); invokeMock.mockResolvedValue(undefined); }); afterEach(() => { vi.unstubAllEnvs(); vi.useRealTimers(); resetSecureSessionStorageForTests(); localStorage.clear(); Reflect.deleteProperty(window, "isTauri"); }); it("stores desktop tokens as a 30 day secure session record", async () => { const token = createJwt(Date.now() + DESKTOP_SESSION_MAX_AGE_MS); await setSessionToken(token); expect(invokeMock).toHaveBeenCalledWith("credential_set", { serverOrigin: SERVER_ORIGIN, token: expect.any(String), }); const stored = JSON.parse(invokeMock.mock.calls[0][1].token); expect(stored).toMatchObject({ version: 1, token }); expect(stored.expiresAt - stored.storedAt).toBe(DESKTOP_SESSION_MAX_AGE_MS); }); it("restores a valid desktop secure session record on startup", async () => { const token = createJwt(Date.now() + DESKTOP_SESSION_MAX_AGE_MS); invokeMock.mockImplementation(async (command: string) => { if (command === "credential_get") { return JSON.stringify({ version: 1, token, storedAt: Date.now(), expiresAt: Date.now() + DESKTOP_SESSION_MAX_AGE_MS, }); } return undefined; }); await initializeSecureSessionStorage(); expect(getSessionToken()).toBe(token); expect(invokeMock).toHaveBeenCalledWith("credential_get", { serverOrigin: SERVER_ORIGIN }); }); it("migrates legacy browser tokens into the desktop credential store", async () => { const token = createJwt(Date.now() + DESKTOP_SESSION_MAX_AGE_MS); localStorage.setItem(LEGACY_TOKEN_KEY, token); await initializeSecureSessionStorage(); expect(localStorage.getItem(LEGACY_TOKEN_KEY)).toBeNull(); expect(getSessionToken()).toBe(token); expect(invokeMock).toHaveBeenCalledWith("credential_set", { serverOrigin: SERVER_ORIGIN, token: expect.any(String), }); const stored = JSON.parse(invokeMock.mock.calls[0][1].token); expect(stored).toMatchObject({ version: 1, token }); }); it("deletes an expired desktop secure session record on startup", async () => { const token = createJwt(Date.now() + DESKTOP_SESSION_MAX_AGE_MS); invokeMock.mockImplementation(async (command: string) => { if (command === "credential_get") { return JSON.stringify({ version: 1, token, storedAt: Date.now() - DESKTOP_SESSION_MAX_AGE_MS - 1_000, expiresAt: Date.now() - 1_000, }); } return undefined; }); await initializeSecureSessionStorage(); expect(getSessionToken()).toBeNull(); expect(invokeMock).toHaveBeenCalledWith("credential_delete", { serverOrigin: SERVER_ORIGIN }); }); it("enforces the local 30 day desktop session ceiling even when the token expires later", async () => { const token = createJwt(Date.now() + DESKTOP_SESSION_MAX_AGE_MS * 2); invokeMock.mockImplementation(async (command: string) => { if (command === "credential_get") { return JSON.stringify({ version: 1, token, storedAt: Date.now() - DESKTOP_SESSION_MAX_AGE_MS - 1_000, expiresAt: Date.now() + DESKTOP_SESSION_MAX_AGE_MS, }); } return undefined; }); await initializeSecureSessionStorage(); expect(getSessionToken()).toBeNull(); expect(invokeMock).toHaveBeenCalledWith("credential_delete", { serverOrigin: SERVER_ORIGIN }); }); it("does not read credentials before a desktop server URL is configured", async () => { localStorage.removeItem(DESKTOP_SERVER_URL_KEY); await initializeSecureSessionStorage(); expect(getSessionToken()).toBeNull(); expect(invokeMock).not.toHaveBeenCalled(); }); it("restores credentials for the build default when no desktop server override is stored", async () => { vi.stubEnv("VITE_DESKTOP_SERVER_URL", "https://default.ctms.example.com"); localStorage.removeItem(DESKTOP_SERVER_URL_KEY); const token = createJwt(Date.now() + DESKTOP_SESSION_MAX_AGE_MS); invokeMock.mockResolvedValue(JSON.stringify({ version: 1, token, storedAt: Date.now(), expiresAt: Date.now() + DESKTOP_SESSION_MAX_AGE_MS, })); await initializeSecureSessionStorage(); expect(getSessionToken()).toBe(token); expect(invokeMock).toHaveBeenCalledExactlyOnceWith("credential_get", { serverOrigin: "https://default.ctms.example.com/", }); }); it("clears the previous server credential after a desktop server switch", async () => { const previousServerOrigin = "https://old.ctms.example.com/"; const nextServerOrigin = "https://new.ctms.example.com/"; const token = createJwt(Date.now() + DESKTOP_SESSION_MAX_AGE_MS); localStorage.setItem(DESKTOP_SERVER_URL_KEY, previousServerOrigin); invokeMock.mockImplementation(async (command: string) => { if (command === "credential_get") { return JSON.stringify({ version: 1, token, storedAt: Date.now(), expiresAt: Date.now() + DESKTOP_SESSION_MAX_AGE_MS, }); } return undefined; }); await initializeSecureSessionStorage(); localStorage.setItem(DESKTOP_SERVER_URL_KEY, nextServerOrigin); await clearSessionToken(); expect(getSessionToken()).toBeNull(); expect(invokeMock).toHaveBeenCalledWith("credential_delete", { serverOrigin: previousServerOrigin }); expect(invokeMock).not.toHaveBeenCalledWith("credential_delete", { serverOrigin: nextServerOrigin }); }); it("rewrites a legacy raw desktop token into a secure session record", async () => { const token = createJwt(Date.now() + DESKTOP_SESSION_MAX_AGE_MS); invokeMock.mockImplementation(async (command: string) => { if (command === "credential_get") return token; return undefined; }); await initializeSecureSessionStorage(); expect(getSessionToken()).toBe(token); expect(invokeMock).toHaveBeenCalledWith("credential_set", { serverOrigin: SERVER_ORIGIN, token: expect.stringContaining(token), }); }); });