import { NextRequest, NextResponse } from "next/server"; /** * Auth gate (PRODUCT-SPEC §24 / ARCHITECTURE.md §3): * - Session = httpOnly `factory_refresh` cookie (refresh token, set by the * API on /auth/login and /auth/register, rotated on /auth/refresh). The * middleware can only check *existence*; the API re-validates every * request. (The cookie is HttpOnly — the client never sees its value; * the access token lives in JS memory only, see security-architecture.md.) * - No cookie + protected path → /login?next=. * - Cookie + auth page → first workspace (or /w/default). */ const SESSION_COOKIE = "factory_refresh"; const PUBLIC_PREFIXES = ["/login", "/register", "/reset-password", "/share/", "/f/", "/health"]; /** Static assets & next internals that never hit the session gate. */ const PUBLIC_PATH_PREFIXES = ["/_next", "/favicon.ico", "/robots.txt", "/sitemap.xml"]; function isPublicPath(pathname: string): boolean { if (PUBLIC_PATH_PREFIXES.some((p) => pathname.startsWith(p))) return true; return PUBLIC_PREFIXES.some((p) => (p.endsWith("/") ? pathname.startsWith(p) : pathname === p)); } export function middleware(request: NextRequest): NextResponse { const { pathname } = request.nextUrl; const hasSession = request.cookies.has(SESSION_COOKIE); // Authenticated users don't need login/register/reset pages. if (hasSession && (pathname === "/login" || pathname === "/register" || pathname === "/reset-password")) { const redirect = request.nextUrl.clone(); redirect.pathname = "/w/default"; redirect.search = ""; return NextResponse.redirect(redirect); } // Unauthenticated visitors are only allowed on public paths. if (!hasSession && !isPublicPath(pathname)) { const login = request.nextUrl.clone(); login.pathname = "/login"; login.search = ""; login.searchParams.set("next", pathname + request.nextUrl.search); return NextResponse.redirect(login); } return NextResponse.next(); } export const config = { matcher: [ // Match everything except static files (handled above for clarity). "/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)" ] };