feat: implement TASK-WP1-04 admin security
This commit is contained in:
@@ -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("白名单");
|
||||
});
|
||||
@@ -35,6 +35,6 @@ test("TDD-WP1-AUTH-003 keeps registration and login as separate keyboard entries
|
||||
await page.keyboard.press("ArrowLeft");
|
||||
await expect(loginTab).toBeFocused();
|
||||
await expect(page.getByRole("textbox", { name: "邀请码" })).toHaveCount(0);
|
||||
await expect(page.getByRole("link", { name: "管理员登录" })).toHaveAttribute("href", "/admin");
|
||||
await expect(page.getByRole("link", { name: "管理员登录" })).toHaveAttribute("href", "/admin/login");
|
||||
await expect(page.getByText("测试数据仅保存在本机,不自动备份,也不会迁移到正式系统。")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ test("TDD-WP1-AUTH-003 renders Z0pf8 login states without silently switching ent
|
||||
await page.goto(webUrl);
|
||||
await expect(page.getByRole("heading", { name: "邮箱验证码登录" })).toBeVisible();
|
||||
await expect(page.getByText("测试数据仅保存在本机,不自动备份,也不会迁移到正式系统。")).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: "管理员登录" })).toHaveAttribute("href", "/admin");
|
||||
await expect(page.getByRole("link", { name: "管理员登录" })).toHaveAttribute("href", "/admin/login");
|
||||
await expect(page.getByLabel("邀请码")).toHaveCount(0);
|
||||
await expect(page.getByLabel("创作署名")).toHaveCount(0);
|
||||
await expect(page.getByLabel("社交 ID")).toHaveCount(0);
|
||||
|
||||
Reference in New Issue
Block a user