You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
47 lines
1.9 KiB
TypeScript
47 lines
1.9 KiB
TypeScript
// frontend/proxy.ts — phase-15 인증 게이트.
|
|
// Next 16에서 `middleware` 파일 규약은 deprecated → `proxy`로 개명됨(node_modules/next/.../proxy.md).
|
|
// 기능은 동일(요청을 라우트 렌더 전에 가로채 리다이렉트). 데모 무손상이 최우선.
|
|
//
|
|
// 동작:
|
|
// - NEXT_PUBLIC_AUTH_ENABLED !== "true" → 즉시 통과(NextResponse.next()). 데모/CI 영향 0.
|
|
// - 활성 + 보호 라우트 + ari_session 쿠키 없음 → /login?next=<경로> 리다이렉트.
|
|
// - /login, /_next, /api, 정적 자산은 matcher에서 제외되어 항상 통과.
|
|
import { NextResponse, type NextRequest } from "next/server";
|
|
|
|
const AUTH_ENABLED = process.env.NEXT_PUBLIC_AUTH_ENABLED === "true";
|
|
const SESSION_COOKIE = "ari_session";
|
|
|
|
export function proxy(request: NextRequest) {
|
|
// 게이트가 꺼져 있으면(기본값) 아무것도 하지 않는다 — 데모 100% 무손상.
|
|
if (!AUTH_ENABLED) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
const { pathname } = request.nextUrl;
|
|
|
|
// /login 은 언제나 허용(무한 리다이렉트 방지). matcher가 _next/api/정적은 이미 제외.
|
|
if (pathname === "/login" || pathname.startsWith("/login/")) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// 세션 쿠키가 있으면 통과(검증은 백엔드 GET /api/me·라우터 의존성이 수행).
|
|
if (request.cookies.get(SESSION_COOKIE)) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// 미인증 → /login?next=<원래 경로+쿼리>
|
|
const next = pathname + request.nextUrl.search;
|
|
const url = request.nextUrl.clone();
|
|
url.pathname = "/login";
|
|
url.search = `?next=${encodeURIComponent(next)}`;
|
|
return NextResponse.redirect(url);
|
|
}
|
|
|
|
export const config = {
|
|
// 보호 대상 = 앱 페이지 전부. 제외: api, _next/static, _next/image, 메타데이터,
|
|
// /login, 그리고 확장자 있는 정적 자산(.svg/.png/.css/.js …).
|
|
matcher: [
|
|
"/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt|login|.*\\..*).*)",
|
|
],
|
|
};
|