forked from baron/baron-sso
개발자 권한을 페이지별로 선택/부여 가능하도록 개선
This commit is contained in:
@@ -12,25 +12,51 @@ describe("developer access gate", () => {
|
||||
});
|
||||
|
||||
it("resolves access and request states from the request status", () => {
|
||||
expect(resolveDeveloperAccessGate("super_admin", "pending")).toEqual({
|
||||
expect(
|
||||
resolveDeveloperAccessGate("super_admin", {
|
||||
status: "pending",
|
||||
pendingPages: ["overview"],
|
||||
}),
|
||||
).toEqual({
|
||||
hasDeveloperAccess: true,
|
||||
isDeveloperRequestPending: true,
|
||||
canRequestDeveloperAccess: false,
|
||||
});
|
||||
|
||||
expect(resolveDeveloperAccessGate("user", "approved")).toEqual({
|
||||
expect(
|
||||
resolveDeveloperAccessGate("user", {
|
||||
status: "approved",
|
||||
approvedPages: ["overview"],
|
||||
}),
|
||||
).toEqual({
|
||||
hasDeveloperAccess: true,
|
||||
isDeveloperRequestPending: false,
|
||||
canRequestDeveloperAccess: false,
|
||||
});
|
||||
|
||||
expect(resolveDeveloperAccessGate("user", "pending")).toEqual({
|
||||
expect(
|
||||
resolveDeveloperAccessGate("user", {
|
||||
status: "pending",
|
||||
pendingPages: ["audit"],
|
||||
}, ["audit"]),
|
||||
).toEqual({
|
||||
hasDeveloperAccess: false,
|
||||
isDeveloperRequestPending: true,
|
||||
canRequestDeveloperAccess: false,
|
||||
});
|
||||
|
||||
expect(resolveDeveloperAccessGate("user", "none")).toEqual({
|
||||
expect(
|
||||
resolveDeveloperAccessGate("user", {
|
||||
status: "approved",
|
||||
approvedPages: ["overview"],
|
||||
}, ["audit"]),
|
||||
).toEqual({
|
||||
hasDeveloperAccess: false,
|
||||
isDeveloperRequestPending: false,
|
||||
canRequestDeveloperAccess: true,
|
||||
});
|
||||
|
||||
expect(resolveDeveloperAccessGate("user", { status: "none" })).toEqual({
|
||||
hasDeveloperAccess: false,
|
||||
isDeveloperRequestPending: false,
|
||||
canRequestDeveloperAccess: true,
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
type DeveloperRequestStatus,
|
||||
type DeveloperAccessStatus,
|
||||
fetchDeveloperRequestStatus,
|
||||
} from "../../lib/devApi";
|
||||
import {
|
||||
hasDeveloperAccessForPages,
|
||||
isDeveloperRequestPendingForPages,
|
||||
type DeveloperAccessPage,
|
||||
} from "./developerAccessPages";
|
||||
|
||||
export type DeveloperAccessGateState = {
|
||||
hasDeveloperAccess: boolean;
|
||||
@@ -14,16 +19,23 @@ export type DeveloperAccessGateState = {
|
||||
|
||||
export function resolveDeveloperAccessGate(
|
||||
profileRole: string,
|
||||
requestStatus?: DeveloperRequestStatus,
|
||||
accessStatus?: DeveloperAccessStatus,
|
||||
requiredPages: DeveloperAccessPage[] = ["overview"],
|
||||
): Omit<
|
||||
DeveloperAccessGateState,
|
||||
"isLoadingDeveloperAccessGate" | "isTenantContextMissing"
|
||||
> {
|
||||
const hasDeveloperAccess =
|
||||
profileRole === "super_admin" || requestStatus === "approved";
|
||||
const isDeveloperRequestPending = requestStatus === "pending";
|
||||
profileRole === "super_admin" ||
|
||||
hasDeveloperAccessForPages(accessStatus?.approvedPages, requiredPages);
|
||||
const isDeveloperRequestPending = isDeveloperRequestPendingForPages(
|
||||
accessStatus?.pendingPages,
|
||||
requiredPages,
|
||||
);
|
||||
const canRequestDeveloperAccess =
|
||||
profileRole === "user" && !hasDeveloperAccess && !isDeveloperRequestPending;
|
||||
profileRole === "user" &&
|
||||
!hasDeveloperAccess &&
|
||||
!isDeveloperRequestPending;
|
||||
|
||||
return {
|
||||
hasDeveloperAccess,
|
||||
@@ -50,11 +62,13 @@ export function useDeveloperAccessGate({
|
||||
hasAccessToken,
|
||||
profileRole,
|
||||
tenantId,
|
||||
requiredPages = ["overview"],
|
||||
isLoadingIdentity = false,
|
||||
}: {
|
||||
hasAccessToken: boolean;
|
||||
profileRole: string;
|
||||
tenantId?: string;
|
||||
requiredPages?: DeveloperAccessPage[];
|
||||
isLoadingIdentity?: boolean;
|
||||
}) {
|
||||
const shouldFetchRequestStatus =
|
||||
@@ -68,7 +82,8 @@ export function useDeveloperAccessGate({
|
||||
|
||||
const resolvedGate = resolveDeveloperAccessGate(
|
||||
profileRole,
|
||||
requestStatus?.status,
|
||||
requestStatus,
|
||||
requiredPages,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeDeveloperAccessPageSelection } from "./developerAccessPages";
|
||||
|
||||
describe("developer access pages", () => {
|
||||
it("collapses all non-all pages into all", () => {
|
||||
expect(
|
||||
normalizeDeveloperAccessPageSelection([
|
||||
"overview",
|
||||
"client_create",
|
||||
"audit",
|
||||
]),
|
||||
).toEqual(["all"]);
|
||||
});
|
||||
|
||||
it("keeps partial selections as-is", () => {
|
||||
expect(
|
||||
normalizeDeveloperAccessPageSelection(["overview", "audit"]),
|
||||
).toEqual(["overview", "audit"]);
|
||||
});
|
||||
|
||||
it("keeps explicit all selection", () => {
|
||||
expect(normalizeDeveloperAccessPageSelection(["all"])).toEqual(["all"]);
|
||||
});
|
||||
});
|
||||
104
devfront/src/features/developer-access/developerAccessPages.ts
Normal file
104
devfront/src/features/developer-access/developerAccessPages.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
export type DeveloperAccessPage =
|
||||
| "all"
|
||||
| "overview"
|
||||
| "client_create"
|
||||
| "audit";
|
||||
|
||||
export const developerAccessPageOrder: DeveloperAccessPage[] = [
|
||||
"overview",
|
||||
"client_create",
|
||||
"audit",
|
||||
];
|
||||
|
||||
export const developerAccessPageOptions: Array<{
|
||||
value: DeveloperAccessPage;
|
||||
label: string;
|
||||
}> = [
|
||||
{ value: "all", label: "전체" },
|
||||
{ value: "overview", label: "개요" },
|
||||
{ value: "client_create", label: "연동 앱 추가" },
|
||||
{ value: "audit", label: "감사로그" },
|
||||
];
|
||||
|
||||
export function normalizeDeveloperAccessPages(
|
||||
pages: Array<string | undefined | null>,
|
||||
): DeveloperAccessPage[] {
|
||||
const normalized = new Set<DeveloperAccessPage>();
|
||||
for (const raw of pages) {
|
||||
const page = String(raw ?? "").trim().toLowerCase();
|
||||
if (!page) {
|
||||
continue;
|
||||
}
|
||||
if (page === "all") {
|
||||
return ["all"];
|
||||
}
|
||||
if (
|
||||
page === "overview" ||
|
||||
page === "client_create" ||
|
||||
page === "audit"
|
||||
) {
|
||||
normalized.add(page);
|
||||
}
|
||||
}
|
||||
|
||||
return [...developerAccessPageOrder.filter((page) => normalized.has(page))];
|
||||
}
|
||||
|
||||
export function normalizeDeveloperAccessPageSelection(
|
||||
pages: DeveloperAccessPage[],
|
||||
): DeveloperAccessPage[] {
|
||||
if (pages.includes("all")) {
|
||||
return ["all"];
|
||||
}
|
||||
const normalized = normalizeDeveloperAccessPages(pages);
|
||||
if (normalized.length === 0) {
|
||||
return ["all"];
|
||||
}
|
||||
if (normalized.length === developerAccessPageOrder.length) {
|
||||
return ["all"];
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function developerAccessPagesToLabel(pages?: Array<string | null>) {
|
||||
const normalized = normalizeDeveloperAccessPages(pages ?? []);
|
||||
if (normalized.length === 0 || normalized.includes("all")) {
|
||||
return "전체";
|
||||
}
|
||||
return normalized
|
||||
.map((page) => {
|
||||
switch (page) {
|
||||
case "overview":
|
||||
return "개요";
|
||||
case "client_create":
|
||||
return "연동 앱 추가";
|
||||
case "audit":
|
||||
return "감사로그";
|
||||
default:
|
||||
return page;
|
||||
}
|
||||
})
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
export function hasDeveloperAccessForPages(
|
||||
grantedPages: Array<string | null> | undefined,
|
||||
requiredPages: DeveloperAccessPage[],
|
||||
) {
|
||||
const normalized = normalizeDeveloperAccessPages(grantedPages ?? []);
|
||||
if (normalized.includes("all")) {
|
||||
return true;
|
||||
}
|
||||
return requiredPages.some((page) => normalized.includes(page));
|
||||
}
|
||||
|
||||
export function isDeveloperRequestPendingForPages(
|
||||
pendingPages: Array<string | null> | undefined,
|
||||
requiredPages: DeveloperAccessPage[],
|
||||
) {
|
||||
const normalized = normalizeDeveloperAccessPages(pendingPages ?? []);
|
||||
if (normalized.includes("all")) {
|
||||
return true;
|
||||
}
|
||||
return requiredPages.some((page) => normalized.includes(page));
|
||||
}
|
||||
Reference in New Issue
Block a user