1
0
forked from baron/baron-sso
Files
baron-sso/devfront/tests/devfront-security.spec.ts

237 lines
7.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
import {
type Consent,
installDevApiMock,
makeClient,
seedAuth,
} from "./helpers/devfront-fixtures";
import { captureEvidence } from "./helpers/evidence";
test.describe("DevFront security and isolation", () => {
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status === "passed") {
await captureEvidence(page, testInfo, testInfo.title);
}
});
test.beforeEach(async ({ page }) => {
page.on("dialog", async (dialog) => {
await dialog.accept();
});
await seedAuth(page);
});
test("tenant isolation: forbidden client shows blocked error", async ({
page,
}) => {
const state = {
clients: [makeClient("tenant-a-client", { name: "Tenant A app" })],
consents: [] as Consent[],
auditLogsByCursor: undefined,
};
await installDevApiMock(page, state);
await page.goto("/clients/tenant-b-client");
await expect(
page.getByText(
/Error loading (app|client)|앱 정보를 불러오지 못했습니다|클라이언트 정보를 불러오지 못했습니다/i,
),
).toBeVisible();
});
test("RBAC: user without manage_all permission should not see private apps", async ({
page,
}) => {
const state = {
clients: [
makeClient("pkce-client", {
name: "PKCE only app",
type: "pkce",
}),
],
consents: [] as Consent[],
auditLogsByCursor: undefined,
};
await installDevApiMock(page, state);
await page.goto("/clients");
await expect(page.getByText("PKCE only app")).toBeVisible();
await expect(page.getByText("Server side App")).not.toBeVisible();
});
test("user can enter DevFront and sees empty RP list", async ({ page }) => {
await seedAuth(page, "user");
const state = {
clients: [] as ReturnType<typeof makeClient>[],
consents: [] as Consent[],
auditLogsByCursor: undefined,
developerRequests: [
{
id: "req-audit-approved",
userId: "playwright-user",
userName: "Playwright User",
name: "Playwright User",
userEmail: "playwright@example.com",
organization: "Tenant A",
reason: "Need access",
status: "approved",
accessPages: ["audit"],
createdAt: "2026-05-29T00:00:00.000Z",
updatedAt: "2026-05-29T00:10:00.000Z",
approvedAt: "2026-05-29T00:10:00.000Z",
},
],
};
await installDevApiMock(page, state);
await page.goto("/clients");
await expect(page).toHaveURL(/\/clients$/);
await expect(
page.getByText(/조회 가능한 RP가 없습니다|No RPs are available/i),
).toBeVisible();
await expect(
page.getByRole("button", { name: /연동 앱 추가|새 클라이언트|Create/i }),
).not.toBeVisible();
});
test("user receives 403 on clients list and sees ForbiddenMessage", async ({
page,
}) => {
await seedAuth(page, "user");
const state = {
clients: [] as ReturnType<typeof makeClient>[],
consents: [] as Consent[],
auditLogsByCursor: undefined,
};
await installDevApiMock(page, state);
await page.route("**/api/v1/dev/clients", async (route) => {
if (route.request().method() === "GET") {
return route.fulfill({
status: 403,
contentType: "application/json",
body: '{"error": "forbidden"}',
});
}
return route.fallback();
});
await page.goto("/clients");
await expect(
page.getByText(/RP 관리자는|RP administrators can only access/i),
).toBeVisible();
});
test("user receives 403 on audit logs and sees ForbiddenMessage", async ({
page,
}) => {
await seedAuth(page, "user");
const state = {
clients: [] as ReturnType<typeof makeClient>[],
consents: [] as Consent[],
auditLogsByCursor: undefined,
};
await installDevApiMock(page, state);
await page.route("**/api/v1/dev/audit-logs*", async (route) => {
if (route.request().method() === "GET") {
return route.fulfill({
status: 403,
contentType: "application/json",
body: '{"error": "forbidden"}',
});
}
return route.fallback();
});
await page.goto("/audit-logs");
await expect(
page.getByText(/테넌트 관리자 권한|Tenant administrator permissions/i),
).toBeVisible();
});
test("user sees audit log access CTA when access is blocked", async ({
page,
}, testInfo) => {
await seedAuth(page, "user");
const state = {
clients: [] as ReturnType<typeof makeClient>[],
consents: [] as Consent[],
auditLogsByCursor: undefined,
developerRequests: [],
};
await installDevApiMock(page, state);
await page.goto("/audit-logs");
await expect(
page.getByRole("heading", { name: /감사 로그|Audit Logs/ }),
).toBeVisible();
await expect(
page.getByText(
/감사 로그는 개발자 권한이 있어야 볼 수 있습니다|Audit logs are available only to users with developer access/i,
),
).toBeVisible();
const requestBtn = page.getByRole("button", {
name: /개발자 권한 신청/,
});
await expect(requestBtn).toBeVisible();
await requestBtn.click();
await expect(page).toHaveURL(/\/developer-requests$/);
await captureEvidence(page, testInfo, "security-user-audit-request-entry");
});
test("user with approved developer request can enter audit logs without CTA", async ({
page,
}, testInfo) => {
await seedAuth(page, "user");
const state = {
clients: [] as ReturnType<typeof makeClient>[],
consents: [] as Consent[],
auditLogs: [
{
event_id: "evt-audit-1",
timestamp: "2026-05-29T00:00:00.000Z",
user_id: "playwright-user",
event_type: "CLIENT_UPDATE",
status: "success" as const,
ip_address: "127.0.0.1",
user_agent: "playwright",
details: JSON.stringify({
action: "UPDATE_CLIENT",
target_id: "tenant-a-client",
tenant_id: "tenant-a",
}),
},
],
auditLogsByCursor: undefined,
developerRequests: [
{
id: "req-approved",
userId: "playwright-user",
userName: "Playwright User",
name: "Playwright User",
userEmail: "playwright@example.com",
organization: "Tenant A",
reason: "Need access",
status: "approved",
createdAt: "2026-05-29T00:00:00.000Z",
updatedAt: "2026-05-29T00:10:00.000Z",
approvedAt: "2026-05-29T00:10:00.000Z",
},
],
};
await installDevApiMock(page, state);
await page.goto("/audit-logs");
await expect(page.getByText("UPDATE_CLIENT")).toBeVisible();
await expect(
page.getByRole("button", { name: /개발자 권한 신청/ }),
).toHaveCount(0);
await captureEvidence(page, testInfo, "security-user-audit-approved");
});
});