import { NextRequest, NextResponse } from "next/server"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? process.env.API_BASE_URL ?? ""; const ACCESS_COOKIE = "oudelaa_sa_access"; const REFRESH_COOKIE = "oudelaa_sa_refresh"; const ACCESS_MAX_AGE_SECONDS = 15 * 60; const REFRESH_MAX_AGE_SECONDS = 30 * 24 * 60 * 60; function buildTargetUrl(pathSegments: string[], searchParams: URLSearchParams) { const base = API_BASE_URL.replace(/\/$/, ""); const path = pathSegments.join("/"); const query = searchParams.toString(); return `${base}/${path}${query ? `?${query}` : ""}`; } function isSuperAdminLogin(pathSegments: string[]) { return pathSegments.join("/") === "auth/superadmin/login"; } function isSuperAdminRefresh(pathSegments: string[]) { return pathSegments.join("/") === "auth/superadmin/refresh"; } function isSuperAdminLogout(pathSegments: string[]) { return pathSegments.join("/") === "auth/superadmin/logout"; } function isJsonContent(contentType: string | null) { return (contentType ?? "").toLowerCase().includes("application/json"); } function parseJsonSafe(value: string): T | null { try { return JSON.parse(value) as T; } catch { return null; } } function buildUpstreamHeaders(params: { req: NextRequest; pathSegments: string[]; accessToken?: string; contentType: string | null; }) { const headers = new Headers(); const { req, pathSegments, accessToken, contentType } = params; const accept = req.headers.get("accept"); if (accept) { headers.set("accept", accept); } if (contentType) { headers.set("content-type", contentType); } const authorization = req.headers.get("authorization"); if (authorization) { headers.set("authorization", authorization); } else if (accessToken && !isSuperAdminLogin(pathSegments)) { headers.set("authorization", `Bearer ${accessToken}`); } return headers; } function applyAuthCookies(response: NextResponse, payload: { accessToken?: string; refreshToken?: string; }) { const secure = process.env.NODE_ENV === "production"; if (payload.accessToken) { response.cookies.set(ACCESS_COOKIE, payload.accessToken, { httpOnly: true, secure, sameSite: "lax", path: "/", maxAge: ACCESS_MAX_AGE_SECONDS, }); } if (payload.refreshToken) { response.cookies.set(REFRESH_COOKIE, payload.refreshToken, { httpOnly: true, secure, sameSite: "lax", path: "/", maxAge: REFRESH_MAX_AGE_SECONDS, }); } } function clearAuthCookies(response: NextResponse) { response.cookies.set(ACCESS_COOKIE, "", { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", path: "/", maxAge: 0, }); response.cookies.set(REFRESH_COOKIE, "", { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "lax", path: "/", maxAge: 0, }); } async function proxyRequest(req: NextRequest, pathSegments: string[] = []) { if (!API_BASE_URL) { return new NextResponse("Missing API base URL", { status: 500 }); } const url = new URL(req.url); const targetUrl = buildTargetUrl(pathSegments, url.searchParams); const isReadOnlyMethod = ["GET", "HEAD"].includes(req.method); const contentType = req.headers.get("content-type"); const accessToken = req.cookies.get(ACCESS_COOKIE)?.value; const refreshToken = req.cookies.get(REFRESH_COOKIE)?.value; const headers = buildUpstreamHeaders({ req, pathSegments, accessToken, contentType, }); let body: BodyInit | undefined; if (!isReadOnlyMethod) { const bodyBuffer = await req.arrayBuffer(); const hasBody = bodyBuffer.byteLength > 0; body = hasBody ? bodyBuffer : undefined; const shouldInjectRefreshToken = (isSuperAdminRefresh(pathSegments) || isSuperAdminLogout(pathSegments)) && isJsonContent(contentType); if (shouldInjectRefreshToken) { const rawText = hasBody ? Buffer.from(bodyBuffer).toString("utf8") : ""; const parsed = parseJsonSafe>(rawText) ?? {}; if (!parsed.refreshToken && refreshToken) { parsed.refreshToken = refreshToken; } body = JSON.stringify(parsed); headers.set("content-type", "application/json"); } } const init: RequestInit = { method: req.method, headers, cache: "no-store", } as RequestInit; if (body !== undefined) { init.body = body; } try { const upstream = await fetch(targetUrl, init); const responseHeaders = new Headers(upstream.headers); responseHeaders.delete("content-encoding"); responseHeaders.delete("content-length"); const isAuthResponse = isSuperAdminLogin(pathSegments) || isSuperAdminRefresh(pathSegments) || isSuperAdminLogout(pathSegments); if (isAuthResponse && isJsonContent(upstream.headers.get("content-type"))) { const payload = (await upstream.json()) as Record; const response = NextResponse.json(payload, { status: upstream.status, headers: responseHeaders, }); if (upstream.ok && (isSuperAdminLogin(pathSegments) || isSuperAdminRefresh(pathSegments))) { applyAuthCookies(response, { accessToken: typeof payload.accessToken === "string" ? payload.accessToken : undefined, refreshToken: typeof payload.refreshToken === "string" ? payload.refreshToken : undefined, }); } if (upstream.ok && isSuperAdminLogout(pathSegments)) { clearAuthCookies(response); } return response; } const response = new NextResponse(upstream.body, { status: upstream.status, headers: responseHeaders, }); if (upstream.ok && isSuperAdminLogout(pathSegments)) { clearAuthCookies(response); } return response; } catch (error) { const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error); return new NextResponse(`Upstream fetch failed: ${message}`, { status: 502 }); } } export async function GET( req: NextRequest, context: { params: Promise<{ path: string[] }> }, ) { const { path } = await context.params; return proxyRequest(req, path ?? []); } export async function POST( req: NextRequest, context: { params: Promise<{ path: string[] }> }, ) { const { path } = await context.params; return proxyRequest(req, path ?? []); } export async function PATCH( req: NextRequest, context: { params: Promise<{ path: string[] }> }, ) { const { path } = await context.params; return proxyRequest(req, path ?? []); } export async function DELETE( req: NextRequest, context: { params: Promise<{ path: string[] }> }, ) { const { path } = await context.params; return proxyRequest(req, path ?? []); } export async function PUT( req: NextRequest, context: { params: Promise<{ path: string[] }> }, ) { const { path } = await context.params; return proxyRequest(req, path ?? []); }