Files
gridpilot.gg/apps/website/middleware.ts
2026-01-18 13:26:35 +01:00

129 lines
4.6 KiB
TypeScript

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)$).*)',
],
};