// Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Project: chat.spboucher.ai // Edge proxy (Next 16 middleware convention): cheap cookie-presence gate + security headers. // Real session validation (DB-backed) happens in every route handler via requireSession(). import { NextResponse, type NextRequest } from "next/server"; const SESSION_COOKIE = "spb_session"; const PUBLIC_PATHS = new Set(["/login", "/api/auth/login", "/manifest.webmanifest", "/sw.js"]); function isPublic(pathname: string): boolean { if (PUBLIC_PATHS.has(pathname)) return true; if (pathname.startsWith("/_next/")) return true; if (pathname.startsWith("/icons/")) return true; if (pathname === "/favicon.ico") return true; return false; } export function proxy(req: NextRequest) { const { pathname } = req.nextUrl; const hasCookie = Boolean(req.cookies.get(SESSION_COOKIE)?.value); let res: NextResponse; if (!isPublic(pathname) && !hasCookie) { if (pathname.startsWith("/api/")) { res = NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } else { const url = req.nextUrl.clone(); url.pathname = "/login"; url.search = ""; res = NextResponse.redirect(url); } } else if (pathname === "/login" && hasCookie) { const url = req.nextUrl.clone(); url.pathname = "/"; url.search = ""; res = NextResponse.redirect(url); } else { res = NextResponse.next(); } res.headers.set("X-Content-Type-Options", "nosniff"); res.headers.set("Referrer-Policy", "no-referrer"); res.headers.set("X-Frame-Options", "DENY"); res.headers.set( "Content-Security-Policy", [ "default-src 'self'", "script-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "font-src 'self' data:", "connect-src 'self'", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", ].join("; ") ); return res; } export const config = { matcher: ["/((?!_next/static|_next/image).*)"], };