2.4 페이지/컴포넌트 가드 훅 구현(usePermission)

This commit is contained in:
koreacomp5
2025-10-09 14:49:31 +09:00
parent 03924c45b0
commit e5cd10fda6
5 changed files with 69 additions and 4 deletions

24
package-lock.json generated
View File

@@ -12,6 +12,7 @@
"next": "15.5.4",
"react": "19.1.0",
"react-dom": "19.1.0",
"swr": "^2.3.6",
"zod": "^3.23.8"
},
"devDependencies": {
@@ -3630,6 +3631,15 @@
"robust-predicates": "^3.0.2"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/destr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
@@ -6492,6 +6502,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/swr": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz",
"integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==",
"license": "MIT",
"dependencies": {
"dequal": "^2.0.3",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/tabbable": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
@@ -6782,7 +6805,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"

View File

@@ -17,10 +17,11 @@
},
"dependencies": {
"@prisma/client": "^6.17.0",
"zod": "^3.23.8",
"next": "15.5.4",
"react": "19.1.0",
"react-dom": "19.1.0"
"react-dom": "19.1.0",
"swr": "^2.3.6",
"zod": "^3.23.8"
},
"devDependencies": {
"@biomejs/biome": "2.2.0",

View File

@@ -0,0 +1,18 @@
import { NextResponse } from "next/server";
import prisma from "@/lib/prisma";
import { getUserIdFromRequest } from "@/lib/auth";
export async function GET(req: Request) {
const userId = getUserIdFromRequest(req);
if (!userId) return NextResponse.json({ permissions: [] });
const roles = await prisma.userRole.findMany({ where: { userId }, select: { roleId: true } });
if (roles.length === 0) return NextResponse.json({ permissions: [] });
const roleIds = roles.map((r) => r.roleId);
const permissions = await prisma.rolePermission.findMany({
where: { roleId: { in: roleIds }, allowed: true },
select: { resource: true, action: true },
});
return NextResponse.json({ permissions });
}

24
src/lib/usePermission.ts Normal file
View File

@@ -0,0 +1,24 @@
"use client";
import useSWR from "swr";
type Resource = "BOARD" | "POST" | "COMMENT" | "USER" | "ADMIN";
type Action = "READ" | "CREATE" | "UPDATE" | "DELETE" | "MODERATE" | "ADMINISTER";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export function usePermission() {
const { data } = useSWR<{ permissions: { resource: Resource; action: Action }[] }>(
"/api/auth/permissions",
fetcher
);
const perms = data?.permissions ?? [];
function can(resource: Resource, action: Action) {
return (
perms.some((p) => p.resource === "ADMIN" && p.action === "ADMINISTER") ||
perms.some((p) => p.resource === resource && p.action === action)
);
}
return { can };
}

View File

@@ -9,7 +9,7 @@
2.1 역할·권한 시드(admin/editor/user) 추가 o
2.2 권한 enum/매핑 정의(리소스/액션) o
2.3 서버 권한 미들웨어 적용(API 보호 라우트 지정) o
2.4 페이지/컴포넌트 가드 훅 구현(usePermission)
2.4 페이지/컴포넌트 가드 훅 구현(usePermission) o
2.5 권한 기반 UI 노출 제어(빠른 액션/관리자 메뉴)
[로그인/인증]