import test from "node:test"; import assert from "node:assert/strict"; import os from "node:os"; import path from "node:path"; import { mkdtemp, rm } from "node:fs/promises"; import { NextRequest } from "next/server"; let runtimeRoot = ""; let data: typeof import("../src/lib/boss-data"); let authCookie = ""; let postLogin: (typeof import("../src/app/api/auth/login/route"))["POST"]; let postRestore: (typeof import("../src/app/api/auth/restore/route"))["POST"]; async function setup() { if (runtimeRoot) return; runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-auth-security-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); const [dataModule, authModule, loginRoute, restoreRoute] = await Promise.all([ import("../src/lib/boss-data.ts"), import("../src/lib/boss-auth.ts"), import("../src/app/api/auth/login/route.ts"), import("../src/app/api/auth/restore/route.ts"), ]); data = dataModule; authCookie = authModule.AUTH_SESSION_COOKIE; postLogin = loginRoute.POST; postRestore = restoreRoute.POST; } test.after(async () => { if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true }); }); test.beforeEach(async () => { await setup(); delete process.env.BOSS_AUTH_AUTO_LOGIN; const now = "2026-04-27T18:00:00+08:00"; const state = await data.readState(); await data.writeState({ ...state, authSessions: [], authAccounts: [ { id: "account-owner", account: "owner@example.com", passwordHash: data.hashPassword("StrongPass123"), displayName: "企业管理员", role: "highest_admin", status: "active", createdAt: now, updatedAt: now, }, { id: "account-mfa", account: "mfa@example.com", passwordHash: data.hashPassword("StrongPass123"), displayName: "MFA 管理员", role: "admin", status: "active", mfaRequired: true, mfaSecret: "test-mfa-secret", createdAt: now, updatedAt: now, }, ], }); }); function loginRequest(body: Record, headers: Record = {}) { return new NextRequest("http://127.0.0.1:3000/api/auth/login", { method: "POST", headers: { "content-type": "application/json", ...headers }, body: JSON.stringify(body), }); } function restoreRequest(restoreToken: string) { return new NextRequest("http://127.0.0.1:3000/api/auth/restore", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ restoreToken }), }); } test("auth mutations reject explicit cross-site browser posts", async () => { const response = await postLogin(loginRequest( { account: "owner@example.com", password: "StrongPass123", method: "password" }, { origin: "https://evil.example", "sec-fetch-site": "cross-site" }, )); assert.equal(response.status, 403); assert.match((await response.json()).message, /CSRF/); }); test("native app login is not blocked by browser CSRF headers", async () => { const response = await postLogin(loginRequest( { account: "owner@example.com", password: "StrongPass123", method: "password" }, { "x-boss-native-app": "1" }, )); assert.equal(response.status, 200); }); test("restore token rotates on every session restore", async () => { const session = await data.createAuthSession({ account: "owner@example.com", role: "highest_admin", displayName: "企业管理员", loginMethod: "password", }); const first = await postRestore(restoreRequest(session.restoreToken)); assert.equal(first.status, 200); const firstPayload = await first.json(); const rotatedToken = firstPayload.session.restoreToken; assert.notEqual(rotatedToken, session.restoreToken); assert.match(first.headers.get("set-cookie") ?? "", new RegExp(`${authCookie}=`)); const oldToken = await postRestore(restoreRequest(session.restoreToken)); assert.equal(oldToken.status, 401); const second = await postRestore(restoreRequest(rotatedToken)); assert.equal(second.status, 200); }); test("MFA-protected accounts require a valid one-time code after password verification", async () => { const missing = await postLogin(loginRequest({ account: "mfa@example.com", password: "StrongPass123", method: "password", })); assert.equal(missing.status, 400); assert.match((await missing.json()).message, /MFA/); const validCode = data.generateAuthAccountMfaCode("test-mfa-secret", new Date()); const passed = await postLogin(loginRequest({ account: "mfa@example.com", password: "StrongPass123", method: "password", mfaCode: validCode, })); assert.equal(passed.status, 200); });