Files
WRNexusJS/packages/auth/test/mfa-resend.test.ts
T

65 lines
2.4 KiB
TypeScript

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 type { AuthDeliveryMessage } from "../src/types.ts";
test("MFA resend creates a fresh code after cooldown and invalidates the previous code", async () => {
let now = 1_790_000_000_000;
const messages: AuthDeliveryMessage[] = [];
const engine = createAuthEngine({
store: new MemoryAuthStore(),
secret: "mfa-resend-test-secret-longer-than-thirty-two-characters",
clock: { now: () => now },
requireTrustedDeviceVerification: true,
delivery: {
async send(message) {
messages.push(message);
},
},
});
await engine.register({ email: "resend@example.test", password: "StrongPassword123!" });
await engine.verifyEmail(messages.find((m) => m.template === "verify-email")!.token!);
const login = await engine.login({
identifier: "resend@example.test",
password: "StrongPassword123!",
fingerprint: "browser",
});
const handlers = createAuthHttpHandlers({ engine });
const request = (resend: boolean) => {
const req = new Request("https://sso.example.test/api/auth/mfa/otp", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ mfaToken: login.mfaToken, method: "email-otp", resend }),
});
return handlers.beginMfaOtp(createContext(req, new URL(req.url)));
};
const first = await (await request(false)).json();
const firstCode = messages.at(-1)!.code!;
const count = messages.length;
expect((await request(true)).status).toBe(400);
expect(messages.length).toBe(count);
expect((await (await request(false)).json()).id).toBe(first.id);
now += 60_000;
const next = await (await request(true)).json();
expect(next.ok).toBe(true);
expect(next.id).not.toBe(first.id);
expect(next.retryAfter).toBe(60);
expect(messages.length).toBe(count + 1);
expect((await engine.verifyOtp(first.id, firstCode, "mfa")).ok).toBe(false);
expect(
(
await engine.completeMfa({
mfaToken: login.mfaToken!,
method: "email-otp",
challengeId: next.id,
code: messages.at(-1)!.code!,
fingerprint: "browser",
})
).ok,
).toBe(true);
now += 60_000;
expect((await request(true)).status).toBe(400);
});