Something went wrong. Try again.
A local-first event pipeline for independent agents, built on Jazz.
Something went wrong. Try again.
7.7 kB · 187 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188import fs from "node:fs/promises";import os from "node:os";import path from "node:path";import { afterEach, describe, expect, test, vi } from "vitest";import { SecureJsonStore } from "../src/web/secure-store.js";
const roots: string[] = [];
afterEach(async () => { await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true })));});
describe("encrypted OAuth store", () => { test("persists authenticated ciphertext with owner-only modes and no plaintext", async () => { const root = await temporaryRoot(); const key = Buffer.alloc(32, 7); const sentinel = "refresh-token-private-sentinel"; const store = storeAt<{ token: string }>(root, "session", key);
await store.set("did:plc:fixture", { token: sentinel });
const file = path.join(root, "session.enc.json"); const raw = await fs.readFile(file, "utf8"); expect(raw).not.toContain(sentinel); expect(raw).not.toContain("did:plc:fixture"); expect((await fs.stat(root)).mode & 0o777).toBe(0o700); expect((await fs.stat(file)).mode & 0o777).toBe(0o600); const restored = storeAt<{ token: string }>(root, "session", key); expect(await restored.get("did:plc:fixture")).toEqual({ token: sentinel });
const wrongKey = storeAt<{ token: string }>(root, "session", Buffer.alloc(32, 8)); await expect(wrongKey.initialize()).rejects.toThrow("authenticated or decoded"); });
test("rejects direct malformed encrypted-envelope fixtures", async () => { const root = await temporaryRoot(); await fs.writeFile(path.join(root, "malformed.enc.json"), "{not-json\n", { mode: 0o600 }); const malformed = storeAt<{ value: string }>(root, "malformed", Buffer.alloc(32, 17)); await expect(malformed.initialize()).rejects.toThrow("envelope is invalid");
await fs.writeFile(path.join(root, "unsupported.enc.json"), JSON.stringify({ version: 1, algorithm: "plaintext", iv: "", ciphertext: "", tag: "", }), { mode: 0o600 }); const unsupported = storeAt<{ value: string }>(root, "unsupported", Buffer.alloc(32, 17)); await expect(unsupported.initialize()).rejects.toThrow("version is unsupported"); });
test("rejects an oversized encrypted-envelope fixture before reading or decrypting it", async () => { const root = await temporaryRoot(); await fs.writeFile(path.join(root, "oversized.enc.json"), "x".repeat(6_000), { mode: 0o600 }); const oversized = new SecureJsonStore<{ value: string }>({ directory: root, name: "oversized", key: Buffer.alloc(32, 18), maxEntries: 2, maxSerializedBytes: 1_024, }); await expect(oversized.initialize()).rejects.toThrow("envelope exceeds its configured bound"); });
test("expires short-lived records and take is one-time", async () => { const root = await temporaryRoot(); let now = 1_000_000; const store = new SecureJsonStore<{ value: string }>({ directory: root, name: "state", key: Buffer.alloc(32, 9), maxEntries: 8, maxSerializedBytes: 8 * 1024, ttlMs: 1_000, now: () => now, });
await store.set("one", { value: "first" }); expect(await store.take("one")).toEqual({ value: "first" }); expect(await store.take("one")).toBeUndefined(); await store.set("two", { value: "second" }); now += 1_001; expect(await store.getWithExpiration("two")).toMatchObject({ expired: true, value: { value: "second" } }); expect(await store.get("two")).toBeUndefined(); });
test("rejects entry-count exhaustion without corrupting accepted state", async () => { const root = await temporaryRoot(); const store = new SecureJsonStore<{ value: string }>({ directory: root, name: "bounded-count", key: Buffer.alloc(32, 10), maxEntries: 2, maxSerializedBytes: 8 * 1024, });
await store.set("a", { value: "one" }); await store.set("b", { value: "two" }); await expect(store.set("c", { value: "three" })).rejects.toThrow("entry limit"); expect(await store.size()).toBe(2); expect(await store.get("a")).toEqual({ value: "one" }); expect(await store.get("c")).toBeUndefined(); });
test("rechecks authority after temporary write and before atomic rename", async () => { const root = await temporaryRoot(); const store = storeAt<{ value: string }>(root, "guarded-write", Buffer.alloc(32, 19)); await store.initialize(); let authoritative = true; const originalWriteFile = fs.writeFile.bind(fs); const writeFile = vi.spyOn(fs, "writeFile").mockImplementation(async (...args: Parameters<typeof fs.writeFile>) => { await originalWriteFile(...args); authoritative = false; }); try { await expect(store.set("late", { value: "must-not-commit" }, () => authoritative)) .rejects.toThrow("lost authority"); } finally { writeFile.mockRestore(); } expect(await store.get("late")).toBeUndefined(); const reopened = storeAt<{ value: string }>(root, "guarded-write", Buffer.alloc(32, 19)); expect(await reopened.get("late")).toBeUndefined(); });
test("has no fallible filesystem operation after the atomic rename commit point", async () => { const root = await temporaryRoot(); const key = Buffer.alloc(32, 15); const store = storeAt<{ value: string }>(root, "rename-commit", key); await store.initialize(); const chmod = vi.spyOn(fs, "chmod").mockRejectedValue(new Error("post-rename chmod must not run")); try { await store.set("committed", { value: "disk-and-memory-agree" }); expect(chmod).not.toHaveBeenCalled(); } finally { chmod.mockRestore(); } expect(await store.get("committed")).toEqual({ value: "disk-and-memory-agree" }); const reopened = storeAt<{ value: string }>(root, "rename-commit", key); expect(await reopened.get("committed")).toEqual({ value: "disk-and-memory-agree" }); expect((await fs.stat(path.join(root, "rename-commit.enc.json"))).mode & 0o777).toBe(0o600); });
test("atomically replaces prior browser-session entries", async () => { const root = await temporaryRoot(); const store = storeAt<{ value: string }>(root, "replace-all", Buffer.alloc(32, 13)); await store.set("old-a", { value: "a" }); await store.set("old-b", { value: "b" }); await store.replaceAll("new", { value: "current" }); expect(await store.size()).toBe(1); expect(await store.get("old-a")).toBeUndefined(); expect(await store.get("old-b")).toBeUndefined(); expect(await store.get("new")).toEqual({ value: "current" }); });
test("rejects serialized-byte exhaustion and rolls back the failed write", async () => { const root = await temporaryRoot(); const store = new SecureJsonStore<{ value: string }>({ directory: root, name: "bounded-bytes", key: Buffer.alloc(32, 11), maxEntries: 8, maxSerializedBytes: 1_024, });
await store.set("small", { value: "retained" }); await expect(store.set("large", { value: "x".repeat(2_000) })).rejects.toThrow("byte limit"); expect(await store.get("small")).toEqual({ value: "retained" }); expect(await store.get("large")).toBeUndefined(); });});
function storeAt<T>(root: string, name: string, key: Buffer): SecureJsonStore<T> { return new SecureJsonStore<T>({ directory: root, name, key, maxEntries: 16, maxSerializedBytes: 64 * 1024, });}
async function temporaryRoot(): Promise<string> { const root = await fs.mkdtemp(path.join(os.tmpdir(), "thoughtstream-oauth-store-")); roots.push(root); return root;}