import { describe, it, expect, beforeAll } from "vitest"; import { signSession, verifySession, COOKIE_NAME, DEFAULT_TTL_SECONDS, type SessionPayload, } from "./auth-cookie"; const SECRET = "test-secret-not-used-anywhere-real"; const NOW = 1_700_000_000; // 2023-11-14 — fixed clock for determinism beforeAll(() => { process.env.AUTH_SECRET = SECRET; process.env.OPERATOR_TOKEN_VERSION = "1"; }); const validPayload = (): SessionPayload => ({ userId: "11111111-1111-1111-1111-111111111111", role: "admin", iat: NOW, exp: NOW + DEFAULT_TTL_SECONDS, v: 1, }); describe("auth-cookie", () => { it("signSession + verifySession round-trips a valid payload", async () => { const cookie = await signSession(validPayload(), SECRET); const verified = await verifySession(cookie, SECRET, NOW); expect(verified).toEqual(validPayload()); }); it("rejects when the payload portion has been tampered with", async () => { const cookie = await signSession(validPayload(), SECRET); // Flip the role to admin → user in the payload, keep the same signature. const [, sig] = cookie.split("."); const tampered = btoa(JSON.stringify({ ...validPayload(), role: "user" })) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, "") + "." + sig; expect(await verifySession(tampered, SECRET, NOW)).toBeNull(); }); it("rejects when the signature has been tampered with", async () => { const cookie = await signSession(validPayload(), SECRET); const [payload] = cookie.split("."); const tampered = payload + ".AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; expect(await verifySession(tampered, SECRET, NOW)).toBeNull(); }); it("rejects when verified with a different secret", async () => { const cookie = await signSession(validPayload(), SECRET); expect(await verifySession(cookie, "different-secret", NOW)).toBeNull(); }); it("rejects an expired cookie (exp <= now)", async () => { const expired = { ...validPayload(), exp: NOW - 1 }; const cookie = await signSession(expired, SECRET); expect(await verifySession(cookie, SECRET, NOW)).toBeNull(); }); it("rejects a cookie issued in the future beyond clock-skew tolerance", async () => { const future = { ...validPayload(), iat: NOW + 120 }; const cookie = await signSession(future, SECRET); expect(await verifySession(cookie, SECRET, NOW)).toBeNull(); }); it("accepts a cookie issued slightly in the future (within 60s skew)", async () => { const future = { ...validPayload(), iat: NOW + 30 }; const cookie = await signSession(future, SECRET); expect(await verifySession(cookie, SECRET, NOW)).not.toBeNull(); }); it("rejects a cookie whose v is stale (token-version bumped)", async () => { const cookie = await signSession({ ...validPayload(), v: 1 }, SECRET); process.env.OPERATOR_TOKEN_VERSION = "2"; expect(await verifySession(cookie, SECRET, NOW)).toBeNull(); process.env.OPERATOR_TOKEN_VERSION = "1"; }); it("rejects a cookie with an unknown role string", async () => { const cookie = await signSession( { ...validPayload(), role: "superadmin" as never }, SECRET, ); expect(await verifySession(cookie, SECRET, NOW)).toBeNull(); }); it("rejects a cookie that doesn't have a '.' separator", async () => { expect(await verifySession("not-a-cookie", SECRET, NOW)).toBeNull(); expect(await verifySession("", SECRET, NOW)).toBeNull(); }); it("exposes COOKIE_NAME as 'session'", () => { expect(COOKIE_NAME).toBe("session"); }); });