import { NextResponse } from "next/server"; import { auth } from "./server/auth-edge.js"; // Paths that are accessible without a session. // Everything else requires a valid JWT session. const PUBLIC_PREFIXES = [ "/auth/", // signin, forgot-password, reset-password "/api/", // tRPC, health, auth endpoints — these manage their own auth "/invite/", // public invite acceptance flow ]; function isPublicPath(pathname: string): boolean { return PUBLIC_PREFIXES.some((prefix) => pathname.startsWith(prefix)); } function buildCsp(nonce: string, isProd: boolean): string { const scriptSrc = isProd ? `'self' 'nonce-${nonce}'` : `'self' 'unsafe-eval' 'unsafe-inline'`; const imgSrc = isProd ? "'self' data: blob:" : "'self' data: blob: https:"; return [ "default-src 'self'", `script-src ${scriptSrc}`, "style-src 'self' 'unsafe-inline'", `img-src ${imgSrc}`, "font-src 'self' data:", "connect-src 'self' https://generativelanguage.googleapis.com https://*.openai.com https://*.azure.com", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", ].join("; "); } export default auth(function middleware(request) { const { pathname } = request.nextUrl; // Redirect unauthenticated requests for protected routes to signin if (!isPublicPath(pathname) && !request.auth) { const signInUrl = new URL("/auth/signin", request.url); signInUrl.searchParams.set("callbackUrl", request.url); return NextResponse.redirect(signInUrl); } // Generate a cryptographically random nonce for this request const nonceBytes = new Uint8Array(16); crypto.getRandomValues(nonceBytes); const nonce = btoa(String.fromCharCode(...nonceBytes)); const isProd = process.env.NODE_ENV === "production"; const csp = buildCsp(nonce, isProd); // Forward nonce to server components via request header const requestHeaders = new Headers(request.headers); requestHeaders.set("x-nonce", nonce); requestHeaders.set("Content-Security-Policy", csp); const response = NextResponse.next({ request: { headers: requestHeaders } }); response.headers.set("Content-Security-Policy", csp); return response; }); export const config = { matcher: [ // Apply to all routes except Next.js internals and static assets "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)", ], };