111 lines
4.3 KiB
TypeScript
111 lines
4.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
authorizeAdminAccess,
|
|
HIGH_RISK_ADMIN_CHALLENGE_TTL_MS,
|
|
issueHighRiskAdminChallenge,
|
|
issueHighRiskAdminProof,
|
|
isSameOriginAdminMutation,
|
|
verifyHighRiskAdminChallenge,
|
|
verifyHighRiskAdminProof,
|
|
} from "../src/lib/admin/auth-policy.ts";
|
|
import type { IdentityUser } from "../src/modules/identity/contracts.ts";
|
|
|
|
const now = 1_786_000_000_000;
|
|
const context = {
|
|
userId: "11111111-1111-4111-8111-111111111111",
|
|
sessionId: "22222222-2222-4222-8222-222222222222",
|
|
permission: "admin.users.manage_roles" as const,
|
|
origin: "https://admin.staging.jyotisha.chat",
|
|
};
|
|
const sessionToken = "better-auth-session-token-held-server-side";
|
|
const proofSecret = "admin-proof-secret-held-only-by-the-server";
|
|
|
|
function user(): IdentityUser {
|
|
return {
|
|
id: context.userId,
|
|
email: "admin@example.com",
|
|
emailVerified: true,
|
|
name: "Admin",
|
|
image: null,
|
|
role: ["user"],
|
|
twoFactorEnabled: false,
|
|
};
|
|
}
|
|
|
|
test("email OTP challenge is short-lived and bound to the requested permission and session", () => {
|
|
const challenge = issueHighRiskAdminChallenge(context, proofSecret, sessionToken, now);
|
|
assert.equal(
|
|
verifyHighRiskAdminChallenge(challenge, context, proofSecret, sessionToken, now + 1_000),
|
|
true,
|
|
);
|
|
assert.equal(verifyHighRiskAdminChallenge(
|
|
challenge,
|
|
{ ...context, permission: "billing.products.publish" },
|
|
proofSecret,
|
|
sessionToken,
|
|
now + 1_000,
|
|
), false);
|
|
assert.equal(verifyHighRiskAdminChallenge(
|
|
challenge,
|
|
context,
|
|
proofSecret,
|
|
"rotated-session-token",
|
|
now + 1_000,
|
|
), false);
|
|
assert.equal(verifyHighRiskAdminChallenge(
|
|
challenge,
|
|
context,
|
|
proofSecret,
|
|
sessionToken,
|
|
now + HIGH_RISK_ADMIN_CHALLENGE_TTL_MS,
|
|
), false);
|
|
});
|
|
|
|
test("high-risk proof succeeds only for its live Better Auth session", () => {
|
|
const proof = issueHighRiskAdminProof(context, proofSecret, sessionToken, now);
|
|
assert.equal(verifyHighRiskAdminProof(proof, context, proofSecret, sessionToken, now + 1_000), true);
|
|
assert.equal(verifyHighRiskAdminProof(proof, { ...context, sessionId: "wrong-session" }, proofSecret, sessionToken, now + 1_000), false);
|
|
assert.equal(verifyHighRiskAdminProof(
|
|
proof,
|
|
{ ...context, permission: "billing.products.publish" },
|
|
proofSecret,
|
|
sessionToken,
|
|
now + 1_000,
|
|
), false);
|
|
assert.equal(verifyHighRiskAdminProof(proof, context, "wrong-server-secret-that-is-long-enough", sessionToken, now + 1_000), false);
|
|
assert.equal(verifyHighRiskAdminProof(proof, context, proofSecret, sessionToken, now + 300_000), false);
|
|
});
|
|
|
|
test("high-risk authorization rejects missing permission and cross-origin requests", () => {
|
|
assert.deepEqual(authorizeAdminAccess(user(), [], context.permission), { allowed: false, status: 403 });
|
|
assert.equal(isSameOriginAdminMutation(context.origin, `${context.origin}/api/admin/reauth`), true);
|
|
assert.equal(isSameOriginAdminMutation("https://evil.example", `${context.origin}/api/admin/reauth`), false);
|
|
assert.equal(isSameOriginAdminMutation(null, `${context.origin}/api/admin/reauth`), false);
|
|
});
|
|
|
|
test("reauth route consumes Better Auth email OTP and sets a scoped HttpOnly proof", () => {
|
|
const route = readFileSync(new URL("../src/app/api/admin/reauth/route.ts", import.meta.url), "utf8");
|
|
const modal = readFileSync(new URL("../src/components/admin/reason-action-modal.tsx", import.meta.url), "utf8");
|
|
|
|
assert.match(route, /sendVerificationOTP/);
|
|
assert.match(route, /verifyEmailOTP/);
|
|
assert.match(route, /type: "email-verification"/);
|
|
assert.match(route, /issueHighRiskAdminChallenge/);
|
|
assert.match(route, /verifyHighRiskAdminChallenge/);
|
|
assert.match(route, /HIGH_RISK_ADMIN_CHALLENGE_COOKIE/);
|
|
assert.match(route, /verifyHighRiskAdminChallenge[\s\S]*verifyEmailOTP/);
|
|
assert.match(route, /httpOnly: true/);
|
|
assert.match(route, /sameSite: "strict"/);
|
|
assert.match(route, /secure: true/);
|
|
assert.match(route, /path: "\/api\/admin"/);
|
|
assert.match(route, /adminProofSigningSecret\(\)/);
|
|
assert.doesNotMatch(route, /console\.|otp.*log|log.*otp/i);
|
|
assert.match(modal, /reauthPermission/);
|
|
assert.match(modal, /action: "request"/);
|
|
assert.match(modal, /action: "verify"/);
|
|
assert.match(modal, /邮箱验证码/);
|
|
});
|