2.4 페이지/컴포넌트 가드 훅 구현(usePermission)
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { requirePermission } from "@/lib/rbac";
|
||||
import { getUserIdFromRequest } from "@/lib/auth";
|
||||
|
||||
export async function POST(_: Request, context: { params: Promise<{ id: string }> }) {
|
||||
export async function POST(req: Request, context: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await context.params;
|
||||
const userId = getUserIdFromRequest(req);
|
||||
try {
|
||||
await requirePermission({ userId, resource: "BOARD", action: "MODERATE" });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
const post = await prisma.post.update({
|
||||
where: { id },
|
||||
data: { status: "published" },
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import prisma from "@/lib/prisma";
|
||||
import { z } from "zod";
|
||||
import { requirePermission } from "@/lib/rbac";
|
||||
import { getUserIdFromRequest } from "@/lib/auth";
|
||||
|
||||
const schema = z.object({ pinned: z.boolean(), order: z.number().int().nullable().optional() });
|
||||
|
||||
@@ -10,6 +12,12 @@ export async function POST(req: Request, context: { params: Promise<{ id: string
|
||||
const parsed = schema.safeParse(body);
|
||||
if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
|
||||
const { pinned, order } = parsed.data;
|
||||
const userId = getUserIdFromRequest(req);
|
||||
try {
|
||||
await requirePermission({ userId, resource: "BOARD", action: "MODERATE" });
|
||||
} catch (e: any) {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
const post = await prisma.post.update({
|
||||
where: { id },
|
||||
data: { isPinned: pinned, pinnedOrder: pinned ? order ?? 0 : null },
|
||||
|
||||
11
src/lib/auth.ts
Normal file
11
src/lib/auth.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// 간이 인증 헬퍼: 실제 세션/쿠키 연동 전 임시로 헤더에서 userId를 읽어옵니다.
|
||||
export function getUserIdFromRequest(req: Request): string | null {
|
||||
try {
|
||||
const id = req.headers.get("x-user-id");
|
||||
return id && id.length > 0 ? id : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
src/lib/rbac.ts
Normal file
55
src/lib/rbac.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
// 권한 확인: userId가 주어지면 역할 → 권한 매핑(RolePermission)으로 검사
|
||||
export async function checkPermission(options: {
|
||||
userId: string | null | undefined;
|
||||
resource: "BOARD" | "POST" | "COMMENT" | "USER" | "ADMIN";
|
||||
action: "READ" | "CREATE" | "UPDATE" | "DELETE" | "MODERATE" | "ADMINISTER";
|
||||
}): Promise<boolean> {
|
||||
const { userId, resource, action } = options;
|
||||
if (!userId) return false;
|
||||
|
||||
const userRoles = await prisma.userRole.findMany({
|
||||
where: { userId },
|
||||
select: { roleId: true },
|
||||
});
|
||||
if (userRoles.length === 0) return false;
|
||||
const roleIds = userRoles.map((r) => r.roleId);
|
||||
const has = await prisma.rolePermission.findFirst({
|
||||
where: {
|
||||
roleId: { in: roleIds },
|
||||
resource,
|
||||
action,
|
||||
allowed: true,
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
if (has) return true;
|
||||
// ADMIN.ADMINISTER 이면 모든 리소스/액션 허용
|
||||
const isAdmin = await prisma.rolePermission.findFirst({
|
||||
where: {
|
||||
roleId: { in: roleIds },
|
||||
resource: "ADMIN",
|
||||
action: "ADMINISTER",
|
||||
allowed: true,
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
return !!isAdmin;
|
||||
}
|
||||
|
||||
export async function requirePermission(options: {
|
||||
userId: string | null | undefined;
|
||||
resource: "BOARD" | "POST" | "COMMENT" | "USER" | "ADMIN";
|
||||
action: "READ" | "CREATE" | "UPDATE" | "DELETE" | "MODERATE" | "ADMINISTER";
|
||||
}): Promise<void> {
|
||||
const ok = await checkPermission(options);
|
||||
if (!ok) {
|
||||
const err = new Error("Forbidden");
|
||||
// @ts-expect-error attach status for route handlers
|
||||
err.status = 403;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
|
||||
[권한/역할(RBAC)]
|
||||
2.1 역할·권한 시드(admin/editor/user) 추가 o
|
||||
2.2 권한 enum/매핑 정의(리소스/액션)
|
||||
2.3 서버 권한 미들웨어 적용(API 보호 라우트 지정)
|
||||
2.2 권한 enum/매핑 정의(리소스/액션) o
|
||||
2.3 서버 권한 미들웨어 적용(API 보호 라우트 지정) o
|
||||
2.4 페이지/컴포넌트 가드 훅 구현(usePermission)
|
||||
2.5 권한 기반 UI 노출 제어(빠른 액션/관리자 메뉴)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user