import { NextRequest, NextResponse } from "next/server"; export const dynamic = "force-dynamic"; export const runtime = "nodejs"; const SESSION_COOKIE = "mc_admin_session"; const SESSION_MAX_AGE = 7 * 24 * 60 * 60; function candidateBases(): string[] { const configured = [ process.env.MASTER_API_URL, process.env.NEXT_PUBLIC_API_URL, "http://master-api:3000", "http://host.docker.internal:3100", ].filter((v): v is string => !!v && v.trim().length > 0); return [...new Set(configured.map((v) => v.replace(/\/$/, "")))]; } function isHttps(request: NextRequest): boolean { const proto = request.headers.get("x-forwarded-proto"); if (proto) return proto.split(",")[0].trim() === "https"; return request.nextUrl.protocol === "https:"; } function applySessionCookie( response: NextResponse, token: string, request: NextRequest, expiresAt?: string ) { const secure = isHttps(request) || process.env.NODE_ENV === "production"; response.cookies.set({ name: SESSION_COOKIE, value: token, httpOnly: true, secure, sameSite: "lax", path: "/", maxAge: SESSION_MAX_AGE, ...(expiresAt ? { expires: new Date(expiresAt) } : {}), }); } function clearSessionCookie(response: NextResponse, request: NextRequest) { const secure = isHttps(request) || process.env.NODE_ENV === "production"; response.cookies.set({ name: SESSION_COOKIE, value: "", httpOnly: true, secure, sameSite: "lax", path: "/", maxAge: 0, }); } async function proxy(request: NextRequest, pathSegments: string[]) { const targetPath = pathSegments.join("/"); const url = new URL(request.url); const search = url.search; const isLogin = request.method === "POST" && targetPath === "v1/auth/login"; const isLogout = request.method === "POST" && targetPath === "v1/auth/logout"; const headers = new Headers(); const contentType = request.headers.get("content-type"); if (contentType) headers.set("content-type", contentType); const cookie = request.headers.get("cookie"); if (cookie) headers.set("cookie", cookie); headers.set("accept", "application/json"); const body = request.method !== "GET" && request.method !== "HEAD" ? await request.arrayBuffer() : undefined; const errors: string[] = []; for (const base of candidateBases()) { const target = `${base}/api/${targetPath}${search}`; try { const upstream = await fetch(target, { method: request.method, headers, body, redirect: "manual", }); // Special-case auth so the session cookie is owned by admin.vonas.nl if (isLogin) { const text = await upstream.text(); let data: { user?: unknown; sessionToken?: string; expiresAt?: string; error?: unknown; } = {}; try { data = JSON.parse(text); } catch { return new NextResponse(text, { status: upstream.status, headers: { "content-type": "application/json" }, }); } const response = NextResponse.json( { user: data.user, error: data.error }, { status: upstream.status } ); if (upstream.ok && data.sessionToken) { applySessionCookie(response, data.sessionToken, request, data.expiresAt); } return response; } if (isLogout) { const responseBody = await upstream.arrayBuffer(); const response = new NextResponse(responseBody, { status: upstream.status, headers: { "content-type": upstream.headers.get("content-type") ?? "application/json", }, }); clearSessionCookie(response, request); return response; } const responseBody = await upstream.arrayBuffer(); const response = new NextResponse(responseBody, { status: upstream.status }); response.headers.set( "content-type", upstream.headers.get("content-type") ?? "application/json" ); // Forward any upstream cookies as fallback (rewritten without Domain) const anyHeaders = upstream.headers as Headers & { getSetCookie?: () => string[]; }; const setCookies = typeof anyHeaders.getSetCookie === "function" ? anyHeaders.getSetCookie() : []; for (const raw of setCookies) { const cleaned = raw .split(";") .map((p) => p.trim()) .filter((p) => !/^domain=/i.test(p)) .join("; "); response.headers.append("set-cookie", cleaned); } return response; } catch (err) { const message = err instanceof Error ? err.message : "fetch failed"; errors.push(`${base}: ${message}`); } } return NextResponse.json( { error: { code: "UPSTREAM_ERROR", message: `Master API unreachable: ${errors.join(" | ")}`, }, }, { status: 502 } ); } type Ctx = { params: Promise<{ path: string[] }> }; export async function GET(request: NextRequest, ctx: Ctx) { const { path } = await ctx.params; return proxy(request, path); } export async function POST(request: NextRequest, ctx: Ctx) { const { path } = await ctx.params; return proxy(request, path); } export async function PUT(request: NextRequest, ctx: Ctx) { const { path } = await ctx.params; return proxy(request, path); } export async function PATCH(request: NextRequest, ctx: Ctx) { const { path } = await ctx.params; return proxy(request, path); } export async function DELETE(request: NextRequest, ctx: Ctx) { const { path } = await ctx.params; return proxy(request, path); }