65 lines
2.4 KiB
TypeScript
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);
|
|
});
|