@restnfeel/agentc-starter-kit
Version:
한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템
216 lines (187 loc) • 5.11 kB
text/typescript
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).*)",
],
};