import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; /** * Paths that do not require an authenticated session. * Checked with startsWith, so /auth/ covers /auth/callback etc. */ const PUBLIC_PATHS: string[] = [ '/login', '/auth/', '/api/auth/', '/api/public/', '/api/health', '/api/webhooks/', '/scan', '/portal/', '/api/portal/', ]; function isPublicPath(pathname: string): boolean { return PUBLIC_PATHS.some((prefix) => pathname === prefix || pathname.startsWith(prefix)); } function isApiRoute(pathname: string): boolean { return pathname.startsWith('/api/'); } export function middleware(request: NextRequest): NextResponse { const { pathname } = request.nextUrl; // Always allow public paths through if (isPublicPath(pathname)) { return NextResponse.next(); } const sessionToken = request.cookies.get('pn-crm.session_token'); if (!sessionToken?.value) { if (isApiRoute(pathname)) { // API routes return 401 JSON — never redirect return NextResponse.json({ error: 'Authentication required' }, { status: 401 }); } // Page routes redirect to /login, preserving the intended destination const loginUrl = new URL('/login', request.url); loginUrl.searchParams.set('redirect', pathname + request.nextUrl.search); return NextResponse.redirect(loginUrl); } return NextResponse.next(); } export const config = { matcher: [ /* * Match all request paths except: * - _next/static (static files) * - _next/image (Next.js image optimisation) * - favicon.ico * - /images/ (public image assets) */ '/((?!_next/static|_next/image|favicon\\.ico|images/).*)', ], };