| import { NextResponse } from "next/server"; |
| import { jwtVerify } from "jose"; |
| import { generateRequestId } from "./shared/utils/requestId"; |
| import { getSettings } from "./lib/localDb"; |
| import { isPublicRoute, verifyAuth, isAuthRequired } from "./shared/utils/apiAuth"; |
| import { checkBodySize, getBodySizeLimit } from "./shared/middleware/bodySizeGuard"; |
| import { isDraining } from "./lib/gracefulShutdown"; |
|
|
| |
| if (!process.env.JWT_SECRET) { |
| console.error("[SECURITY] JWT_SECRET is not set. Authentication will fail."); |
| } |
|
|
| const SECRET = new TextEncoder().encode(process.env.JWT_SECRET); |
|
|
| export async function proxy(request) { |
| const { pathname } = request.nextUrl; |
|
|
| |
| const requestId = generateRequestId(); |
| const response = NextResponse.next(); |
| response.headers.set("X-Request-Id", requestId); |
|
|
| |
| if (isDraining() && pathname.startsWith("/api/")) { |
| return NextResponse.json( |
| { |
| error: { |
| code: "SERVICE_UNAVAILABLE", |
| message: "Server is shutting down", |
| correlation_id: requestId, |
| }, |
| }, |
| { status: 503 } |
| ); |
| } |
|
|
| |
| if (pathname.startsWith("/api/") && request.method !== "GET" && request.method !== "OPTIONS") { |
| const bodySizeRejection = checkBodySize(request, getBodySizeLimit(pathname)); |
| if (bodySizeRejection) return bodySizeRejection; |
| } |
|
|
| |
| if (pathname.startsWith("/api/") && !pathname.startsWith("/api/v1/")) { |
| |
| if (isPublicRoute(pathname)) { |
| return response; |
| } |
|
|
| |
| const authRequired = await isAuthRequired(); |
| if (!authRequired) { |
| return response; |
| } |
|
|
| |
| const authError = await verifyAuth(request); |
| if (authError) { |
| return NextResponse.json( |
| { |
| error: { |
| code: "AUTH_001", |
| message: authError, |
| correlation_id: requestId, |
| }, |
| }, |
| { status: 401 } |
| ); |
| } |
| } |
|
|
| |
| if (pathname.startsWith("/dashboard")) { |
| |
| if (pathname.startsWith("/dashboard/onboarding")) { |
| return response; |
| } |
|
|
| const token = request.cookies.get("auth_token")?.value; |
|
|
| if (token) { |
| try { |
| await jwtVerify(token, SECRET); |
| return response; |
| } catch (err) { |
| |
| console.error("[Middleware] auth_error: JWT verification failed:", err.message, { |
| path: pathname, |
| tokenPresent: true, |
| requestId, |
| }); |
| return NextResponse.redirect(new URL("/login", request.url)); |
| } |
| } |
|
|
| try { |
| |
| const settings = await getSettings(); |
| |
| if (settings.requireLogin === false) { |
| return response; |
| } |
| |
| |
| if (!settings.password && !process.env.INITIAL_PASSWORD) { |
| return response; |
| } |
| } catch (err) { |
| |
| console.error("[Middleware] settings_error: Settings read failed:", err.message, { |
| path: pathname, |
| requestId, |
| }); |
| |
| } |
| return NextResponse.redirect(new URL("/login", request.url)); |
| } |
|
|
| |
| if (pathname === "/") { |
| return NextResponse.redirect(new URL("/dashboard", request.url)); |
| } |
|
|
| return response; |
| } |
|
|
| export const config = { |
| matcher: ["/", "/dashboard/:path*", "/api/:path*"], |
| }; |
|
|