import { describe, expect, it } from "vitest"; import { ADMIN_SSO_CHALLENGE_COOKIE, ADMIN_SSO_CHALLENGE_TTL_MS, consumeAdminSsoChallenge, createAdminSsoChallenge, serializeAdminSsoChallengeCookie, } from "./state.server"; const SECRET = "test-secret-do-not-use-in-prod"; function cookieValue(cookie: string): string { const head = cookie.split(";")[0] ?? ""; return head.slice(`${ADMIN_SSO_CHALLENGE_COOKIE}=`.length); } function requestWithCookie(value: string): Request { return new Request("https://admin.example.test/auth/admin/callback", { headers: { cookie: `${ADMIN_SSO_CHALLENGE_COOKIE}=${value}` }, }); } describe("createAdminSsoChallenge", () => { it("produces distinct state, nonce and codeVerifier for each invocation", () => { const a = createAdminSsoChallenge({ secret: SECRET }); const b = createAdminSsoChallenge({ secret: SECRET }); expect(a.payload.state).not.toBe(b.payload.state); expect(a.payload.nonce).not.toBe(b.payload.nonce); expect(a.payload.codeVerifier).not.toBe(b.payload.codeVerifier); // state should be 32 random bytes hex-encoded -> 64 chars expect(a.payload.state).toMatch(/^[0-9a-f]{64}$/); expect(a.payload.nonce).toMatch(/^[0-9a-f]{64}$/); }); it("derives codeChallenge from codeVerifier (PKCE S256)", () => { const { payload } = createAdminSsoChallenge({ secret: SECRET }); expect(payload.codeChallenge).toMatch(/^[A-Za-z0-9_-]+$/); expect(payload.codeChallenge).not.toBe(payload.codeVerifier); }); it("emits an HttpOnly, Path-scoped cookie", () => { const { cookie } = createAdminSsoChallenge({ secret: SECRET }); expect(cookie).toMatch(/HttpOnly/); expect(cookie).toMatch(/Path=\/auth\/admin/); expect(cookie).toMatch(/SameSite=Lax/); }); }); describe("consumeAdminSsoChallenge", () => { it("round-trips a fresh challenge and returns a clear-cookie header", () => { const { payload, cookie } = createAdminSsoChallenge({ secret: SECRET, returnTo: "/admin/dashboard" }); const value = cookieValue(cookie); const req = requestWithCookie(value); const result = consumeAdminSsoChallenge(req, { secret: SECRET }); expect(result.ok).toBe(true); if (result.ok) { expect(result.payload.state).toBe(payload.state); expect(result.payload.nonce).toBe(payload.nonce); expect(result.payload.codeVerifier).toBe(payload.codeVerifier); expect(result.payload.returnTo).toBe("/admin/dashboard"); expect(result.clearCookie).toContain(`${ADMIN_SSO_CHALLENGE_COOKIE}=`); expect(result.clearCookie).toContain("Max-Age=0"); } }); it("reports `missing` when no cookie is attached", () => { const req = new Request("https://admin.example.test/auth/admin/callback"); const result = consumeAdminSsoChallenge(req, { secret: SECRET }); expect(result.ok).toBe(false); if (!result.ok) expect(result.reason).toBe("missing"); }); it("rejects a cookie with a tampered signature", () => { const { cookie } = createAdminSsoChallenge({ secret: SECRET }); const value = cookieValue(cookie); const dot = value.lastIndexOf("."); const tampered = value.slice(0, dot) + ".AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; const result = consumeAdminSsoChallenge(requestWithCookie(tampered), { secret: SECRET }); expect(result.ok).toBe(false); if (!result.ok) expect(result.reason).toBe("bad-signature"); }); it("rejects a cookie signed under a different secret", () => { const { cookie } = createAdminSsoChallenge({ secret: SECRET }); const result = consumeAdminSsoChallenge(requestWithCookie(cookieValue(cookie)), { secret: "different-secret" }); expect(result.ok).toBe(false); if (!result.ok) expect(result.reason).toBe("bad-signature"); }); it("rejects an expired cookie (older than TTL)", () => { const past = Date.now() - ADMIN_SSO_CHALLENGE_TTL_MS - 1000; const { payload } = createAdminSsoChallenge({ secret: SECRET, now: () => past }); const cookie = serializeAdminSsoChallengeCookie(payload, SECRET); const result = consumeAdminSsoChallenge(requestWithCookie(cookieValue(cookie)), { secret: SECRET }); expect(result.ok).toBe(false); if (!result.ok) expect(result.reason).toBe("expired"); }); it("rejects a malformed cookie value", () => { const result = consumeAdminSsoChallenge(requestWithCookie("garbage"), { secret: SECRET }); expect(result.ok).toBe(false); if (!result.ok) expect(["malformed", "bad-signature"]).toContain(result.reason); }); });