import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; import { SessionGateway } from '@/lib/gateways/SessionGateway'; import { handleAuthFlow } from '@/lib/auth/AuthFlowRouter'; import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger'; import { routeMatchers } from '@/lib/routing/RouteConfig'; import { SearchParamBuilder } from '@/lib/routing/search-params/SearchParamBuilder'; const logger = new ConsoleLogger(); /** * Server-side route protection middleware * * Uses UnifiedLoginStateMachine for deterministic, type-safe authentication flow */ export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; const cookieHeader = request.headers.get('cookie') || ''; // logger.info('[MIDDLEWARE] ========== REQUEST START =========='); // logger.info('[MIDDLEWARE] Request details', { // pathname, // method: request.method, // url: request.url, // cookieHeaderLength: cookieHeader.length, // cookiePreview: cookieHeader.substring(0, 100) + (cookieHeader.length > 100 ? '...' : '') // }); // Set x-pathname header for layout-level protection const response = NextResponse.next(); response.headers.set('x-pathname', pathname); // Get session // logger.info('[MIDDLEWARE] Fetching session...'); const sessionGateway = new SessionGateway(); const session = await sessionGateway.getSessionFromRequest(request); // logger.info('[MIDDLEWARE] Session fetched', { // hasSession: !!session, // userId: session?.user?.userId, // role: session?.user?.role, // sessionData: session ? JSON.stringify(session, null, 2) : 'null' // }); // Convert session to state machine format const authSession = session ? { userId: session.user?.userId || '', role: session.user?.role || 'driver', roles: session.user?.role ? [session.user.role] : ['driver'] } : null; // logger.info('[MIDDLEWARE] Auth session converted', { // authSession: authSession ? JSON.stringify(authSession, null, 2) : 'null' // }); // Debug: Log route classification const isPublic = routeMatchers.isPublic(pathname); const requiresRole = routeMatchers.requiresRole(pathname); // logger.info('[MIDDLEWARE] Route classification', { // path: pathname, // isPublic, // requiresRole // }); // Use state machine to determine action let result; try { // logger.info('[MIDDLEWARE] Calling handleAuthFlow...'); result = handleAuthFlow(authSession, pathname); // logger.info('[MIDDLEWARE] handleAuthFlow result', { // result: JSON.stringify(result, null, 2) // }); } catch (error) { logger.error('[MIDDLEWARE] Error in auth flow', error instanceof Error ? error : new Error(String(error))); // Fallback: redirect to login if there's an error return NextResponse.redirect(new URL(`/auth/login${SearchParamBuilder.auth(pathname)}`, request.url)); } // logger.info('[MIDDLEWARE] Decision summary', { // pathname, // hasSession: !!authSession, // role: authSession?.role, // shouldRedirect: result.shouldRedirect, // redirectUrl: result.redirectUrl // }); if (result.shouldRedirect && result.redirectUrl) { const redirectUrl = new URL(result.redirectUrl, request.url); // logger.info('[MIDDLEWARE] REDIRECTING', { // from: pathname, // to: redirectUrl.toString() // }); const redirectResponse = NextResponse.redirect(redirectUrl); // logger.info('[MIDDLEWARE] ========== REQUEST END (REDIRECT) =========='); return redirectResponse; } // Handle /sponsor root redirect (preserves cookies) // Only reached if authenticated and allowed if (pathname === '/sponsor') { // logger.info('[MIDDLEWARE] Redirecting /sponsor → /sponsor/dashboard'); return NextResponse.redirect(new URL('/sponsor/dashboard', request.url)); } // All checks passed // logger.info('[MIDDLEWARE] ALLOWING ACCESS', { pathname }); // logger.info('[MIDDLEWARE] ========== REQUEST END (ALLOW) =========='); return response; } /** * Configure which routes the middleware should run on */ export const config = { matcher: [ /* * Match all request paths except: * - _next/static (static files) * - _next/image (image optimization files) * - _next/data (Next.js data requests) * - favicon.ico (favicon file) * - health (health check endpoint) * - media (media assets) * - Files with extensions (static assets) */ '/((?!_next/static|_next/image|_next/data|favicon.ico|health|media|.*\\.(?:svg|png|jpg|jpeg|gif|webp|mp4|webm|mov|avi)$).*)', ], };