import { EventEmitter } from "node:events"; import { PassThrough } from "node:stream"; import { describe, expect, test, vi } from "vitest"; import { createWindowsAuthStorageBridge } from "../src/auth/windows-auth-storage.js"; const root = "C:\\ProgramData\\ThothII\\auth"; const filename = "a".repeat(64) + ".json"; class FakeBridgeChild extends EventEmitter { readonly stdin = new PassThrough(); readonly stdout = new PassThrough(); readonly stderr = new PassThrough(); readonly kill = vi.fn(() => true); close(code = 0, signal: NodeJS.Signals | null = null): void { this.emit("close", code, signal); } } function bridgeForChild(child: FakeBridgeChild) { const spawnChild = vi.fn(() => child); const bridge = createWindowsAuthStorageBridge({ thtExecutable: "C:\\tht.exe", spawnChild, } as never); return { bridge, spawnChild }; } describe("Windows auth-storage bridge", () => { test("uses hidden tht argv and sends record bytes only over bounded stdin", async () => { const calls: Array<{ executable: string; args: readonly string[]; input: Buffer; timeoutMs: number }> = []; const bridge = createWindowsAuthStorageBridge({ thtExecutable: "C:\\Program Files\\ThothII\\tht.exe", invoke: async (call) => { calls.push(call); return { code: 0, stdout: Buffer.from('{"version":1,"ok":true,"created":true}\n'), stderr: Buffer.alloc(0) }; }, }); await expect(bridge.create(root, "sessions", filename, Buffer.from('{"subject":"record-data"}'))).resolves.toBe(true); expect(calls).toHaveLength(1); expect(calls[0]).toMatchObject({ executable: "C:\\Program Files\\ThothII\\tht.exe", args: ["_auth-storage"], }); expect(JSON.stringify(calls[0].args)).not.toContain("record-data"); expect(JSON.parse(calls[0].input.toString("utf8"))).toMatchObject({ version: 1, operation: "create", root, directory: "sessions", filename, contentBase64: Buffer.from('{"subject":"record-data"}').toString("base64"), }); expect(calls[0].timeoutMs).toBeGreaterThan(0); }); test.each([ { label: "nonzero", result: { code: 1, stdout: Buffer.from('{"version":1,"ok":true}\n'), stderr: Buffer.from("secret") } }, { label: "malformed stdout", result: { code: 0, stdout: Buffer.from("not-json"), stderr: Buffer.alloc(0) } }, { label: "unexpected stdout", result: { code: 0, stdout: Buffer.from('{"version":1,"ok":true}\nextra'), stderr: Buffer.alloc(0) } }, { label: "unexpected JSON field", result: { code: 0, stdout: Buffer.from('{"version":1,"ok":true,"created":true,"detail":"secret"}\n'), stderr: Buffer.alloc(0) } }, ])("fails closed on $label bridge output", async ({ result }) => { const bridge = createWindowsAuthStorageBridge({ thtExecutable: "C:\\tht.exe", invoke: async () => result, }); await expect(bridge.create(root, "sessions", filename, Buffer.from("record"))) .rejects.toThrow("auth_session_store_invalid"); }); test("fails closed on a bridge timeout without disclosing request content", async () => { const bridge = createWindowsAuthStorageBridge({ thtExecutable: "C:\\tht.exe", invoke: async () => { throw new Error("timeout secret-record"); }, }); await expect(bridge.create(root, "sessions", filename, Buffer.from("secret-record"))) .rejects.toThrow("auth_session_store_invalid"); }); test("rejects a claimed-read response without bounded record bytes", async () => { const bridge = createWindowsAuthStorageBridge({ thtExecutable: "C:\\tht.exe", invoke: async () => ({ code: 0, stdout: Buffer.from('{"version":1,"ok":true,"found":true}\n'), stderr: Buffer.alloc(0) }), }); await expect(bridge.readClaim(root, filename)).rejects.toThrow("auth_session_store_invalid"); }); test("rejects an executable value that would require shell parsing", () => { expect(() => createWindowsAuthStorageBridge({ thtExecutable: "tht.exe && unexpected" })) .toThrow("auth_session_store_invalid"); }); test("parses the lower-camel list DTO emitted by the Go helper for a nonempty directory", async () => { const bridge = createWindowsAuthStorageBridge({ thtExecutable: "C:\\tht.exe", invoke: async () => ({ code: 0, // This is the raw JSON object emitted by authstorage.response after Go's DTO encoding. stdout: Buffer.from(`{"version":1,"ok":true,"entries":[{"name":"${filename}","modifiedUnixMs":1893456245000}]}\n`), stderr: Buffer.alloc(0), }), }); await expect(bridge.list(root, "oidc")).resolves.toEqual([ { name: filename, modifiedUnixMs: 1_893_456_245_000 }, ]); }); test("aborts a stdin-closed looping helper on timeout and waits for close", async () => { vi.useFakeTimers(); const child = new FakeBridgeChild(); const { bridge, spawnChild } = bridgeForChild(child); const pending = bridge.list(root, "sessions"); const outcome = pending.then(() => "resolved", () => "rejected"); try { await vi.advanceTimersByTimeAsync(5_000); expect(spawnChild).toHaveBeenCalledOnce(); expect(child.kill).toHaveBeenCalledOnce(); expect(child.stdin.destroyed).toBe(true); await expect(Promise.race([outcome, Promise.resolve("pending")])).resolves.toBe("pending"); child.close(); await expect(outcome).resolves.toBe("rejected"); } finally { child.close(); vi.useRealTimers(); } }); test.each(["stdout", "stderr"] as const)("aborts a %s-flooding helper and waits for close", async (stream) => { const child = new FakeBridgeChild(); const { bridge, spawnChild } = bridgeForChild(child); const pending = bridge.list(root, "sessions"); const outcome = pending.then(() => "resolved", () => "rejected"); await Promise.resolve(); expect(spawnChild).toHaveBeenCalledOnce(); child[stream].write(Buffer.alloc(64 * 1024 + 1)); await Promise.resolve(); expect(child.kill).toHaveBeenCalledOnce(); expect(child.stdin.destroyed).toBe(true); await expect(Promise.race([outcome, Promise.resolve("pending")])).resolves.toBe("pending"); child.close(); await expect(outcome).resolves.toBe("rejected"); }); test("aborts a helper when its stdin errors and waits for termination", async () => { const child = new FakeBridgeChild(); const { bridge, spawnChild } = bridgeForChild(child); const stdinErrored = new Promise((resolve) => child.stdin.once("error", () => resolve())); child.stdin.once("finish", () => child.stdin.destroy(new Error("stdin failure"))); const pending = bridge.list(root, "sessions"); const outcome = pending.then(() => "resolved", () => "rejected"); await Promise.resolve(); expect(spawnChild).toHaveBeenCalledOnce(); await stdinErrored; expect(child.kill).toHaveBeenCalledOnce(); child.close(); await expect(outcome).resolves.toBe("rejected"); }); test("aborts an errored child exactly once and waits for its close event", async () => { const child = new FakeBridgeChild(); const { bridge, spawnChild } = bridgeForChild(child); const pending = bridge.list(root, "sessions"); const outcome = pending.then(() => "resolved", () => "rejected"); await Promise.resolve(); expect(spawnChild).toHaveBeenCalledOnce(); child.emit("error", new Error("helper error")); child.emit("error", new Error("duplicate helper error")); expect(child.kill).toHaveBeenCalledOnce(); await expect(Promise.race([outcome, Promise.resolve("pending")])).resolves.toBe("pending"); child.close(); await expect(outcome).resolves.toBe("rejected"); }); test("fails closed when launching the helper throws before a child exists", async () => { const bridge = createWindowsAuthStorageBridge({ thtExecutable: "C:\\tht.exe", spawnChild: () => { throw new Error("launch detail must not escape"); }, }); await expect(bridge.list(root, "sessions")).rejects.toThrow("auth_session_store_invalid"); }); });