forked from baron/baron-sso
custom claim 권한체크 확인
This commit is contained in:
@@ -50,6 +50,43 @@ test("clients page loads correctly", async ({ page }) => {
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("clients page shows Tenant-limited only for tenant access restricted RP", async ({
|
||||
page,
|
||||
}) => {
|
||||
await seedAuth(page, "super_admin");
|
||||
await installDevApiMock(page, {
|
||||
clients: [
|
||||
makeClient("client-limited", {
|
||||
name: "Limited RP",
|
||||
createdAt: "2026-05-02T00:00:00.000Z",
|
||||
metadata: {
|
||||
tenant_access_restricted: true,
|
||||
allowed_tenants: ["tenant-1"],
|
||||
},
|
||||
}),
|
||||
makeClient("client-open", {
|
||||
name: "Open RP",
|
||||
createdAt: "2026-05-01T00:00:00.000Z",
|
||||
metadata: {
|
||||
tenant_access_restricted: false,
|
||||
allowed_tenants: [],
|
||||
},
|
||||
}),
|
||||
],
|
||||
consents: [] as Consent[],
|
||||
auditLogsByCursor: undefined,
|
||||
});
|
||||
|
||||
await page.goto("/clients");
|
||||
|
||||
const limitedRow = page.locator("tbody tr", { hasText: "Limited RP" });
|
||||
await expect(limitedRow).toContainText("Tenant-limited");
|
||||
|
||||
const openRow = page.locator("tbody tr", { hasText: "Open RP" });
|
||||
await expect(openRow).not.toContainText("Tenant-limited");
|
||||
await expect(page.getByText("Tenant-scoped")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("overview page shows recent RP changes", async ({ page }) => {
|
||||
await seedAuth(page, "super_admin");
|
||||
await installDevApiMock(page, {
|
||||
|
||||
63
devfront/tests/devfront-client-claims-cache.spec.ts
Normal file
63
devfront/tests/devfront-client-claims-cache.spec.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import {
|
||||
type Consent,
|
||||
installDevApiMock,
|
||||
makeClient,
|
||||
seedAuth,
|
||||
} from "./helpers/devfront-fixtures";
|
||||
import { installDevFrontStaticRoutes } from "./helpers/static-devfront";
|
||||
|
||||
test.describe("DevFront RP claim cache", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await installDevFrontStaticRoutes(page);
|
||||
await seedAuth(page, "super_admin");
|
||||
});
|
||||
|
||||
test("keeps saved RP claim value visible after saving", async ({ page }) => {
|
||||
const state = {
|
||||
clients: [
|
||||
makeClient("client-claims", {
|
||||
name: "Claims app",
|
||||
metadata: {
|
||||
id_token_claims: [
|
||||
{
|
||||
namespace: "rp_claims",
|
||||
key: "old_claim",
|
||||
value: "A",
|
||||
valueType: "text",
|
||||
readPermission: "admin_only",
|
||||
writePermission: "admin_only",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
consents: [] as Consent[],
|
||||
auditLogsByCursor: undefined,
|
||||
mockRole: "super_admin",
|
||||
};
|
||||
await installDevApiMock(page, state);
|
||||
|
||||
await page.goto("http://devfront.test/clients/client-claims/settings");
|
||||
|
||||
const claimKeyInput = page
|
||||
.getByPlaceholder(/e\.g\. locale|예: locale/i)
|
||||
.first();
|
||||
await expect(claimKeyInput).toHaveValue("old_claim");
|
||||
|
||||
await claimKeyInput.fill("new_claim");
|
||||
await page.getByRole("button", { name: /^저장$|^Save$/i }).click();
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
(
|
||||
state.clients[0]?.metadata?.id_token_claims as
|
||||
| Array<{ key?: string }>
|
||||
| undefined
|
||||
)?.[0]?.key,
|
||||
)
|
||||
.toBe("new_claim");
|
||||
await expect(claimKeyInput).toHaveValue("new_claim");
|
||||
});
|
||||
});
|
||||
@@ -155,14 +155,21 @@ test.describe("DevFront clients lifecycle", () => {
|
||||
.getByLabel(/Claim value type|Claim 값 타입/i)
|
||||
.first()
|
||||
.selectOption("date");
|
||||
await expect(
|
||||
page.getByRole("columnheader", { name: /Default Value|기본값/i }),
|
||||
).toBeVisible();
|
||||
await page
|
||||
.getByPlaceholder(/Claim 값을 입력하세요|Enter the claim value/i)
|
||||
.getByPlaceholder(/기본값을 입력하세요|Enter the default value/i)
|
||||
.first()
|
||||
.fill("2026-06-09");
|
||||
await page
|
||||
.getByLabel(/읽기 권한|Read permission/i)
|
||||
.getByLabel(/Nullable|Null 허용/i)
|
||||
.first()
|
||||
.selectOption("user_and_admin");
|
||||
.click();
|
||||
await page
|
||||
.getByLabel(/Read 사용자 허용|Read user allowed/i)
|
||||
.first()
|
||||
.click();
|
||||
|
||||
await page.getByRole("button", { name: /Claim 추가|Add Claim/i }).click();
|
||||
await page
|
||||
@@ -174,7 +181,7 @@ test.describe("DevFront clients lifecycle", () => {
|
||||
.nth(1)
|
||||
.selectOption("number");
|
||||
await page
|
||||
.getByPlaceholder(/Claim 값을 입력하세요|Enter the claim value/i)
|
||||
.getByPlaceholder(/기본값을 입력하세요|Enter the default value/i)
|
||||
.nth(1)
|
||||
.fill("2");
|
||||
|
||||
@@ -238,6 +245,7 @@ test.describe("DevFront clients lifecycle", () => {
|
||||
key?: string;
|
||||
value?: string;
|
||||
valueType?: string;
|
||||
nullable?: boolean;
|
||||
readPermission?: string;
|
||||
writePermission?: string;
|
||||
}>
|
||||
@@ -245,6 +253,18 @@ test.describe("DevFront clients lifecycle", () => {
|
||||
)?.[0]?.valueType,
|
||||
)
|
||||
.toBe("date");
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
(
|
||||
state.clients[0]?.metadata?.id_token_claims as
|
||||
| Array<{
|
||||
nullable?: boolean;
|
||||
}>
|
||||
| undefined
|
||||
)?.[0]?.nullable,
|
||||
)
|
||||
.toBe(true);
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
@@ -313,18 +333,25 @@ test.describe("DevFront clients lifecycle", () => {
|
||||
page.getByPlaceholder(/e\.g\. locale|예: locale/i).nth(1),
|
||||
).toHaveValue("tier");
|
||||
await expect(
|
||||
page.getByPlaceholder(/Claim 값을 입력하세요|Enter the claim value/i),
|
||||
page.getByPlaceholder(/기본값을 입력하세요|Enter the default value/i),
|
||||
).toHaveCount(2);
|
||||
await expect(
|
||||
page
|
||||
.getByPlaceholder(/Claim 값을 입력하세요|Enter the claim value/i)
|
||||
.getByPlaceholder(/기본값을 입력하세요|Enter the default value/i)
|
||||
.first(),
|
||||
).toHaveValue("2026-06-09");
|
||||
await expect(
|
||||
page
|
||||
.getByPlaceholder(/Claim 값을 입력하세요|Enter the claim value/i)
|
||||
.getByPlaceholder(/기본값을 입력하세요|Enter the default value/i)
|
||||
.nth(1),
|
||||
).toHaveValue("2");
|
||||
await expect(page.getByLabel(/Nullable|Null 허용/i).first()).toBeChecked();
|
||||
await expect(
|
||||
page.getByLabel(/Read 사용자 허용|Read user allowed/i).first(),
|
||||
).toBeChecked();
|
||||
await expect(
|
||||
page.getByLabel(/Write 사용자 허용|Write user allowed/i).first(),
|
||||
).not.toBeChecked();
|
||||
});
|
||||
|
||||
test("headless login uses jwks uri only and shows cache actions", async ({
|
||||
|
||||
@@ -40,6 +40,18 @@ test.describe("DevFront consents", () => {
|
||||
valueType: "datetime",
|
||||
value: "2026-06-09T09:30",
|
||||
},
|
||||
{
|
||||
namespace: "rp_claims",
|
||||
key: "active_member",
|
||||
valueType: "boolean",
|
||||
value: "true",
|
||||
},
|
||||
{
|
||||
namespace: "rp_claims",
|
||||
key: "score",
|
||||
valueType: "number",
|
||||
value: "1",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
@@ -78,9 +90,14 @@ test.describe("DevFront consents", () => {
|
||||
await expect(page.getByText("RP Custom Claims")).toBeVisible();
|
||||
await expect(page.getByText("contract_date")).toBeVisible();
|
||||
await expect(page.getByText("approved_at")).toBeVisible();
|
||||
await expect(page.getByText("active_member")).toBeVisible();
|
||||
await expect(page.locator('input[type="date"]')).toHaveValue("2026-06-09");
|
||||
await page.locator('input[type="date"]').fill("2026-06-10");
|
||||
await page.locator('input[type="datetime-local"]').fill("2026-06-09T10:30");
|
||||
await page
|
||||
.getByLabel(/active_member.*boolean|boolean.*active_member/i)
|
||||
.selectOption("false");
|
||||
await page.getByLabel(/score.*number|number.*score/i).fill("42");
|
||||
await page
|
||||
.getByLabel(/쓰기 권한|Write permission/i)
|
||||
.first()
|
||||
@@ -92,6 +109,10 @@ test.describe("DevFront consents", () => {
|
||||
await expect
|
||||
.poll(() => state.consents[0]?.rpMetadata?.approved_at)
|
||||
.toBe("2026-06-09T10:30");
|
||||
await expect
|
||||
.poll(() => state.consents[0]?.rpMetadata?.active_member)
|
||||
.toBe(false);
|
||||
await expect.poll(() => state.consents[0]?.rpMetadata?.score).toBe(42);
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
|
||||
@@ -466,6 +466,7 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
createdAt: client.createdAt,
|
||||
redirectUris: client.redirectUris,
|
||||
scopes: client.scopes,
|
||||
metadata: client.metadata ?? {},
|
||||
})),
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
@@ -612,6 +613,7 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
token: "https://issuer/oauth2/token",
|
||||
userinfo: "https://issuer/userinfo",
|
||||
},
|
||||
headlessJwksCache: found.headlessJwksCache,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -635,6 +637,7 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
token: "https://issuer/oauth2/token",
|
||||
userinfo: "https://issuer/userinfo",
|
||||
},
|
||||
headlessJwksCache: found.headlessJwksCache,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -720,6 +723,7 @@ export async function installDevApiMock(page: Page, state: DevApiMockState) {
|
||||
token: "https://issuer/oauth2/token",
|
||||
userinfo: "https://issuer/userinfo",
|
||||
},
|
||||
headlessJwksCache: found.headlessJwksCache,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
93
devfront/tests/helpers/static-devfront.ts
Normal file
93
devfront/tests/helpers/static-devfront.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { readFile, stat } from "node:fs/promises";
|
||||
import { extname, join, normalize, resolve } from "node:path";
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
const contentTypes: Record<string, string> = {
|
||||
".css": "text/css; charset=utf-8",
|
||||
".html": "text/html; charset=utf-8",
|
||||
".ico": "image/x-icon",
|
||||
".js": "application/javascript; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".map": "application/json; charset=utf-8",
|
||||
".mjs": "application/javascript; charset=utf-8",
|
||||
".png": "image/png",
|
||||
".svg": "image/svg+xml",
|
||||
".txt": "text/plain; charset=utf-8",
|
||||
".webp": "image/webp",
|
||||
".woff": "font/woff",
|
||||
".woff2": "font/woff2",
|
||||
};
|
||||
|
||||
function safeDistPath(distDir: string, pathname: string) {
|
||||
const decoded = decodeURIComponent(pathname);
|
||||
const relative = decoded.replace(/^\/+/, "");
|
||||
const safe = normalize(relative).replace(/^(\.\.(?:[\\/]|$))+/, "");
|
||||
return join(distDir, safe);
|
||||
}
|
||||
|
||||
async function resolveStaticFile(distDir: string, pathname: string) {
|
||||
const indexPath = join(distDir, "index.html");
|
||||
let filePath = safeDistPath(
|
||||
distDir,
|
||||
pathname === "/" ? "/index.html" : pathname,
|
||||
);
|
||||
|
||||
try {
|
||||
const fileStat = await stat(filePath);
|
||||
if (fileStat.isDirectory()) {
|
||||
filePath = join(filePath, "index.html");
|
||||
}
|
||||
} catch {
|
||||
filePath = indexPath;
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
body: await readFile(filePath),
|
||||
contentType:
|
||||
contentTypes[extname(filePath).toLowerCase()] ??
|
||||
"application/octet-stream",
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function installDevFrontStaticRoutes(
|
||||
page: Page,
|
||||
options: {
|
||||
distDir?: string;
|
||||
origin?: string;
|
||||
} = {},
|
||||
) {
|
||||
const origin = options.origin ?? "http://devfront.test";
|
||||
const distDir = resolve(
|
||||
options.distDir ??
|
||||
process.env.DEVFRONT_DIST_DIR ??
|
||||
"/tmp/baron-sso-devfront-dist",
|
||||
);
|
||||
|
||||
await page.route(`${origin}/**`, async (route) => {
|
||||
const url = new URL(route.request().url());
|
||||
if (url.pathname === "/api" || url.pathname.startsWith("/api/")) {
|
||||
await route.fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
const file = await resolveStaticFile(distDir, url.pathname);
|
||||
if (!file) {
|
||||
await route.fulfill({
|
||||
status: 500,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: JSON.stringify({ error: "devfront_dist_not_found" }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: file.contentType,
|
||||
body: file.body,
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user