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 { generateTotp } from "../src/totp/index.ts"; test("HTTP login creates a usable MFA challenge; expired requests require a new login", async () => { let time = 1_720_000_000_000; const engine = createAuthEngine({ store: new MemoryAuthStore(), secret: "mfa-full-flow-test-secret-longer-than-thirty-two-characters", clock: { now: () => time }, }); const user = await engine.register({ email: "flow@example.test", password: "StrongPassword123" }); const setup = await engine.beginTotp(user.user!.id); expect( await engine.confirmTotp( user.user!.id, setup.credentialId, await generateTotp(setup.secret, { timestamp: time }), ), ).toBe(true); const handlers = createAuthHttpHandlers({ engine }); const context = (path: string, body: unknown) => { const req = new Request(`https://sso.example.test${path}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), }); return createContext(req, new URL(req.url)); }; const login = async () => { const response = await handlers.login( context("/api/auth/login", { identifier: "flow@example.test", password: "StrongPassword123", }), ); expect(response.status).toBe(401); const result = await response.json(); expect(result.code).toBe("mfa-required"); expect(result.mfaExpiresAt).toBe(time + 300_000); return result; }; const old = await login(); time += 300_001; const expired = await handlers.completeMfa( context("/api/auth/mfa/complete", { mfaToken: old.mfaToken, method: "totp", code: await generateTotp(setup.secret, { timestamp: time }), }), ); expect((await expired.json()).code).toBe("mfa-transaction-expired"); const fresh = await login(); const success = await handlers.completeMfa( context("/api/auth/mfa/complete", { mfaToken: fresh.mfaToken, method: "totp", code: await generateTotp(setup.secret, { timestamp: time }), }), ); expect(success.status).toBe(200); expect(await success.json()).toMatchObject({ ok: true, user: { id: user.user!.id } }); });