import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; import { SessionGateway } from '@/lib/gateways/SessionGateway'; import { routes, routeMatchers } from '@/lib/routing/RouteConfig'; /** * Server-side route protection middleware * * This middleware provides comprehensive route protection by: * 1. Setting x-pathname header for layout-level protection * 2. Checking authentication status via SessionGateway * 3. Redirecting unauthenticated users from protected routes * 4. Redirecting authenticated users away from auth pages * 5. Handling role-based access control */ export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; // Debug logging console.log(`[MIDDLEWARE] Processing request for path: ${pathname}`); // Handle /sponsor root redirect to /sponsor/dashboard in middleware to preserve cookies if (pathname === '/sponsor') { console.log(`[MIDDLEWARE] Redirecting /sponsor to /sponsor/dashboard`); return NextResponse.redirect(new URL('/sponsor/dashboard', request.url)); } // Set x-pathname header for layout-level protection const response = NextResponse.next(); response.headers.set('x-pathname', pathname); // Get session first (needed for all auth-related decisions) const sessionGateway = new SessionGateway(); const session = await sessionGateway.getSessionFromRequest(request); console.log(`[MIDDLEWARE] Session retrieved:`, session ? 'Session found' : 'No session'); if (session) { console.log(`[MIDDLEWARE] User role:`, session.user?.role); } // Auth pages (login, signup, etc.) - handle before public check if (routeMatchers.isInGroup(pathname, 'auth')) { if (session) { // Check if user was redirected here due to insufficient permissions const returnTo = request.nextUrl.searchParams.get('returnTo'); if (returnTo) { // User has a session but insufficient permissions for the returnTo route // Allow them to see the login page (they may need to switch accounts) console.log(`[MIDDLEWARE] Authenticated user at login with returnTo, allowing access`); return response; } // User is authenticated and navigated to auth page directly, redirect away const role = session.user?.role || 'driver'; const redirectPath = getHomePathForRole(role); // Preserve locale if present in the original path const localeMatch = pathname.match(/^\/([a-z]{2})\//); if (localeMatch) { const locale = localeMatch[1]; return NextResponse.redirect(new URL(`/${locale}${redirectPath}`, request.url)); } return NextResponse.redirect(new URL(redirectPath, request.url)); } // Unauthenticated users can access auth pages return response; } // Public routes (no auth required, but not auth pages) if (routeMatchers.isPublic(pathname)) { console.log(`[MIDDLEWARE] Route is public, allowing access`); return response; } // Protected routes (require authentication) if (!session) { // No session, redirect to login console.log(`[MIDDLEWARE] No session, redirecting to login`); // Preserve locale if present in the path const localeMatch = pathname.match(/^\/([a-z]{2})\//); const locale = localeMatch ? localeMatch[1] : null; const redirectUrl = new URL(routes.auth.login, request.url); redirectUrl.searchParams.set('returnTo', pathname); // If locale is present, include it in the redirect if (locale) { redirectUrl.pathname = `/${locale}${redirectUrl.pathname}`; } console.log(`[MIDDLEWARE] Redirecting to:`, redirectUrl.toString()); return NextResponse.redirect(redirectUrl); } // Role-based access control const requiredRoles = routeMatchers.requiresRole(pathname); console.log(`[MIDDLEWARE] Required roles for ${pathname}:`, requiredRoles); if (requiredRoles) { const userRole = session.user?.role; console.log(`[MIDDLEWARE] User role:`, userRole); if (!userRole || !requiredRoles.includes(userRole)) { // User doesn't have required role or no role at all, redirect to login console.log(`[MIDDLEWARE] User doesn't have required role, redirecting to login`); // Preserve locale if present in the path const localeMatch = pathname.match(/^\/([a-z]{2})\//); const locale = localeMatch ? localeMatch[1] : null; const redirectUrl = new URL(routes.auth.login, request.url); redirectUrl.searchParams.set('returnTo', pathname); if (locale) { redirectUrl.pathname = `/${locale}${redirectUrl.pathname}`; } console.log(`[MIDDLEWARE] Redirecting to:`, redirectUrl.toString()); return NextResponse.redirect(redirectUrl); } } // All checks passed, allow access console.log(`[MIDDLEWARE] All checks passed, allowing access`); return response; } /** * Get the home path for a specific role */ function getHomePathForRole(role: string): string { const roleHomeMap: Record = { 'driver': routes.protected.dashboard, 'sponsor': routes.sponsor.dashboard, 'league-admin': routes.admin.root, 'league-steward': routes.admin.root, 'league-owner': routes.admin.root, 'system-owner': routes.admin.root, 'super-admin': routes.admin.root, }; return roleHomeMap[role] || routes.protected.dashboard; } /** * 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)$).*)', ], };