import { expect, test } from "bun:test"; import { createContext } from "@wrnexus/core"; import { createAuthEngine } from "../src/engine.ts"; import { createAuthHttpHandlers } from "../src/http/index.ts"; import { MemoryAuthStore } from "../src/stores/memory.ts"; import { getVerificationUser, VERIFICATION_SESSION_COOKIE } from "../src/verification-session.ts"; import type { AuthDeliveryMessage } from "../src/types.ts"; test("onSignUp issues a persistent verification-only cookie and registration receives a trusted link origin", async () => { const store = new MemoryAuthStore(); const messages: AuthDeliveryMessage[] = []; let hookCalls = 0; const options = { store, secret: "signup-session-test-secret-longer-than-32-characters", requireVerifiedEmail: true, onSignUp() { hookCalls++; return { verificationSession: true, redirectTo: "/verify-email" }; }, delivery: { async send(message: AuthDeliveryMessage) { messages.push(message); }, }, }; const engine = createAuthEngine(options); const url = new URL("https://sso.example.test/api/auth/register"); const ctx = createContext( new Request(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ displayName: "Signup Test", email: "signup@example.test", password: "StrongPassword123!", consent: true, baseUrl: "https://attacker.example", }), }), url, ); const response = await createAuthHttpHandlers({ engine }).register(ctx); expect(response.status).toBe(303); expect(response.headers.get("location")).toBe("https://sso.example.test/verify-email"); expect(hookCalls).toBe(1); expect(messages[0]!.url).toBe( `https://sso.example.test/verify-email?token=${messages[0]!.token}`, ); expect(messages[0]!.url).not.toContain("attacker"); const setCookie = ctx.cookies .headers() .filter((value) => value.startsWith(`${VERIFICATION_SESSION_COOKIE}=`)) .at(-1)!; expect(setCookie).toContain("HttpOnly"); expect(setCookie).toContain("Secure"); expect(setCookie).toContain("SameSite=Lax"); const cookie = setCookie.split(";")[0]!; const restartedEngine = createAuthEngine(options); const nextUrl = new URL("https://sso.example.test/api/auth/verification/request"); const next = createContext( new Request(nextUrl, { method: "POST", headers: { cookie, "content-type": "application/json" }, body: JSON.stringify({ type: "email", identifier: "attacker@example.test" }), }), nextUrl, ); const pending = await getVerificationUser(next, restartedEngine); expect(pending?.id).toBe(messages[0]!.user.id); expect(next.user).toBeUndefined(); expect( (await createAuthHttpHandlers({ engine: restartedEngine }).requestVerification(next)).status, ).toBe(200); expect(messages[1]!.destination).toBe("signup@example.test"); const token = decodeURIComponent(cookie.slice(cookie.indexOf("=") + 1)); expect(await restartedEngine.validateSession(token)).toBeUndefined(); expect((await restartedEngine.verifyEmail(token)).ok).toBe(false); expect((await restartedEngine.verifyEmail(messages[1]!.token!)).ok).toBe(true); expect(await getVerificationUser(next, restartedEngine)).toBeUndefined(); }); test("verification sessions reject expiry, tampering, wrong purpose, and revoked tokens", async () => { let time = 100_000; const engine = createAuthEngine({ store: new MemoryAuthStore(), secret: "signup-session-test-secret-longer-than-32-characters", clock: { now: () => time }, }); const user = ( await engine.register({ email: "expiry@example.test", password: "StrongPassword123!" }) ).user!; const token = await engine.createVerificationSession(user.id); expect(await engine.validateVerificationSession(`${token}x`)).toBeUndefined(); expect( await engine.validateVerificationSession(await engine.issueToken(user.id, "verify-email")), ).toBeUndefined(); time += 30 * 60_000 + 1; expect(await engine.validateVerificationSession(token)).toBeUndefined(); const renewed = await engine.createVerificationSession(user.id); await engine.consumeToken(renewed, "verification-session"); expect(await engine.validateVerificationSession(renewed)).toBeUndefined(); });