UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

216 lines (187 loc) 5.11 kB
import { NextRequest, NextResponse } from "next/server"; import { getToken } from "next-auth/jwt"; // 사용자 역할 타입 export enum UserRole { ADMIN = "ADMIN", EDITOR = "EDITOR", VIEWER = "VIEWER", GUEST = "GUEST", } // 라우트 보호 설정 interface RouteProtection { path: string; requiredRoles?: UserRole[]; isPublic?: boolean; redirectTo?: string; } // 보호된 라우트 설정 const protectedRoutes: RouteProtection[] = [ // 관리자 전용 { path: "/admin", requiredRoles: [UserRole.ADMIN], redirectTo: "/auth/signin", }, { path: "/api/admin", requiredRoles: [UserRole.ADMIN], redirectTo: "/auth/signin", }, // 편집자 이상 { path: "/editor", requiredRoles: [UserRole.ADMIN, UserRole.EDITOR], redirectTo: "/auth/signin", }, { path: "/api/content", requiredRoles: [UserRole.ADMIN, UserRole.EDITOR], redirectTo: "/auth/signin", }, // 인증된 사용자 전용 { path: "/dashboard", requiredRoles: [UserRole.ADMIN, UserRole.EDITOR, UserRole.VIEWER], redirectTo: "/auth/signin", }, { path: "/profile", requiredRoles: [UserRole.ADMIN, UserRole.EDITOR, UserRole.VIEWER], redirectTo: "/auth/signin", }, // 공개 라우트 { path: "/auth", isPublic: true, }, { path: "/api/auth", isPublic: true, }, { path: "/", isPublic: true, }, ]; /** * 인증 미들웨어 */ export async function authMiddleware(request: NextRequest) { const { pathname } = request.nextUrl; try { // JWT 토큰 확인 const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET, }); // 해당 경로의 보호 설정 찾기 const routeConfig = findRouteConfig(pathname); // 공개 라우트인 경우 통과 if (routeConfig?.isPublic) { return NextResponse.next(); } // 인증이 필요한 라우트인데 토큰이 없는 경우 if (!token) { const redirectUrl = routeConfig?.redirectTo || "/auth/signin"; const loginUrl = new URL(redirectUrl, request.url); loginUrl.searchParams.set("callbackUrl", pathname); return NextResponse.redirect(loginUrl); } // 역할 기반 접근 제어 if (routeConfig?.requiredRoles) { const userRole = token.role as UserRole; const hasRequiredRole = routeConfig.requiredRoles.includes(userRole); if (!hasRequiredRole) { // 권한 부족 시 접근 거부 return new NextResponse("Forbidden", { status: 403 }); } } // 인증 및 권한 확인 통과 return NextResponse.next(); } catch (error) { console.error("Authentication middleware error:", error); // 에러 발생 시 로그인 페이지로 리디렉션 const loginUrl = new URL("/auth/signin", request.url); loginUrl.searchParams.set("error", "AuthenticationError"); loginUrl.searchParams.set("callbackUrl", pathname); return NextResponse.redirect(loginUrl); } } /** * 경로에 맞는 라우트 설정 찾기 */ function findRouteConfig(pathname: string): RouteProtection | undefined { // 정확한 경로 매치 먼저 시도 let config = protectedRoutes.find((route) => pathname === route.path); if (config) return config; // 경로 prefix 매치 시도 config = protectedRoutes.find( (route) => pathname.startsWith(route.path + "/") || pathname.startsWith(route.path + "?") ); return config; } /** * API 라우트용 권한 검증 헬퍼 */ export async function requireAuth( request: NextRequest, requiredRoles?: UserRole[] ) { try { const token = await getToken({ req: request, secret: process.env.NEXTAUTH_SECRET, }); if (!token) { return { error: "Unauthorized", status: 401 }; } if (requiredRoles) { const userRole = token.role as UserRole; const hasRequiredRole = requiredRoles.includes(userRole); if (!hasRequiredRole) { return { error: "Forbidden", status: 403 }; } } return { user: token, error: null }; } catch (error) { console.error("Auth verification error:", error); return { error: "Authentication failed", status: 500 }; } } /** * 관리자 권한 검증 */ export async function requireAdmin(request: NextRequest) { return requireAuth(request, [UserRole.ADMIN]); } /** * 편집자 이상 권한 검증 */ export async function requireEditor(request: NextRequest) { return requireAuth(request, [UserRole.ADMIN, UserRole.EDITOR]); } /** * 인증된 사용자 검증 */ export async function requireUser(request: NextRequest) { return requireAuth(request, [ UserRole.ADMIN, UserRole.EDITOR, UserRole.VIEWER, ]); } // 미들웨어 matcher 설정 export const config = { matcher: [ /* * 다음 경로들을 제외한 모든 요청에 매치: * - api (API routes) * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) */ "/((?!api|_next/static|_next/image|favicon.ico).*)", ], };