import { describe, expect, it } from "vitest"; import { compareTraces, parseTrace, redactTrace, type AuthenticationTrace, } from "../../src/webauthn/trace"; function trace(): AuthenticationTrace { return { schema: "de.add-ideas.auth-tools.webauthn-trace", version: 1, kind: "authentication", recordedAt: "2026-08-19T12:00:00.000Z", request: { user: { id: "user-id", name: "Alice", displayName: "Alice Example" }, extensions: { prf: true }, }, expectations: { challenge: "AQID", origin: "https://auth.example.test", rpId: "auth.example.test", }, credentialPublicKey: { "1": 2, "3": -7, "-1": 1 }, response: { id: "credential-id", rawId: "credential-id", clientDataJSON: "e30", authenticatorData: "AQID", signature: "BAUG", userHandle: "user-handle", clientExtensionResults: { prf: { enabled: true } }, }, privacy: [], }; } describe("WebAuthn ceremony traces", () => { it("parses the bounded versioned schema", () => { expect(parseTrace(JSON.stringify(trace()))).toMatchObject({ kind: "authentication", version: 1, }); expect(() => parseTrace('{"version":2}')).toThrow(/schema/u); let nested: Record = {}; for (let depth = 0; depth < 70; depth += 1) nested = { nested }; expect(() => parseTrace(JSON.stringify({ ...trace(), request: nested })), ).toThrow(/depth limit/u); }); it("redacts labels and top-level identifiers while preserving evidence", async () => { const redacted = await redactTrace(trace()); expect(redacted.response.id).toMatch(/^sha256:/u); expect(redacted.response.clientDataJSON).toBe("e30"); expect( redacted.kind === "authentication" && redacted.response.userHandle, ).toBe("[redacted]"); expect((redacted.request.user as Record).displayName).toBe( "[redacted]", ); }); it("compares nested trace state", () => { const right = trace(); right.expectations.rpId = "other.example.test"; right.response.clientExtensionResults = { prf: { enabled: false } }; expect(compareTraces(trace(), right).map(({ path }) => path)).toEqual([ "expectations.rpId", "response.clientExtensionResults.prf.enabled", ]); }); });