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