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) * - Files with extensions (static assets) */ '/((?!_next/static|_next/image|_next/data|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|mp4|webm|mov|avi)$).*)', ], };