feat: implement TASK-WP1-04 admin security

This commit is contained in:
suyx
2026-07-28 18:32:50 +08:00
parent 66fe3b763a
commit 03f1509de7
29 changed files with 2344 additions and 40 deletions
+74
View File
@@ -0,0 +1,74 @@
import { expect, test } from "@playwright/test";
import { createServer, type ViteDevServer } from "vite";
import { resolve } from "node:path";
let vite: ViteDevServer;
let webUrl: string;
test.beforeAll(async () => {
vite = await createServer({
configFile: resolve("apps/web/vite.config.ts"),
root: resolve("apps/web"),
server: { host: "127.0.0.1", port: 0 },
});
await vite.listen();
const address = vite.httpServer?.address();
if (!address || typeof address === "string") throw new Error("Vite did not expose a test port.");
webUrl = `http://127.0.0.1:${address.port}`;
});
test.afterAll(async () => vite.close());
test("TDD-WP1-ADM-001 renders the isolated admin login and hands success to /admin", async ({ page }) => {
await page.route("**/api/v1/admin-auth/login/send", (route) => route.fulfill({
contentType: "application/json",
status: 200,
body: JSON.stringify({
challenge_expires_at: "2026-07-28T12:10:00.000Z",
registration_id: "00000000-0000-4000-8000-000000000007",
resend_available_at: "2026-07-28T12:01:00.000Z",
status: "verification_sent",
}),
}));
await page.route("**/api/v1/admin-auth/login/complete", (route) => route.fulfill({
contentType: "application/json",
status: 200,
body: JSON.stringify({ audience: "admin", status: "authenticated" }),
}));
await page.goto(`${webUrl}/admin/login`);
await expect(page.getByRole("heading", { name: "管理员邮箱验证码登录" })).toBeVisible();
await expect(page.locator(".auth-art")).toHaveCount(0);
await expect(page.getByRole("link", { name: "返回普通用户登录" })).toHaveAttribute("href", "/");
await page.getByRole("textbox", { name: "管理员邮箱" }).fill("admin-ui@example.invalid");
const sendButton = page.getByRole("button", { name: "获取验证码" });
const widthBefore = (await sendButton.boundingBox())?.width;
await sendButton.click();
const codeInput = page.getByRole("textbox", { name: "验证码" });
await expect(codeInput).toBeVisible();
expect((await sendButton.boundingBox())?.width).toBe(widthBefore);
await codeInput.fill("418205");
await page.getByRole("button", { name: "登录后台" }).click();
await expect(page).toHaveURL(`${webUrl}/admin`);
});
test("TDD-WP1-ADM-001 shows a generic allowlist rejection without exposing identity state", async ({ page }) => {
await page.route("**/api/v1/admin-auth/login/send", (route) => route.fulfill({
contentType: "application/json",
status: 409,
body: JSON.stringify({
error: {
code: "AUTH_ENTRY_REJECTED",
correlation_id: "00000000-0000-4000-8000-000000000008",
details: { field_errors: [{ field: "email", message_key: "admin.auth.not_allowed" }] },
message_key: "auth.entry_rejected",
},
}),
}));
await page.goto(`${webUrl}/admin/login`);
await page.getByRole("textbox", { name: "管理员邮箱" }).fill("not-admin@example.invalid");
await page.getByRole("button", { name: "获取验证码" }).click();
await expect(page.getByRole("alert")).toContainText("无法使用管理员入口");
await expect(page.getByRole("alert")).not.toContainText("普通用户");
await expect(page.getByRole("alert")).not.toContainText("白名单");
});