middleware fix wip
This commit is contained in:
@@ -1,27 +1,33 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { SessionGateway } from '@/lib/gateways/SessionGateway';
|
||||
import { routes, routeMatchers } from '@/lib/routing/RouteConfig';
|
||||
import { handleAuthFlow } from '@/lib/auth/AuthFlowRouter';
|
||||
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
|
||||
import { routeMatchers } from '@/lib/routing/RouteConfig';
|
||||
|
||||
const logger = new ConsoleLogger();
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* Uses UnifiedLoginStateMachine for deterministic, type-safe authentication flow
|
||||
*/
|
||||
export async function middleware(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
const cookieHeader = request.headers.get('cookie') || '';
|
||||
|
||||
// Debug logging
|
||||
console.log(`[MIDDLEWARE] Processing request for path: ${pathname}`);
|
||||
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 ? '...' : '')
|
||||
});
|
||||
|
||||
// Handle /sponsor root redirect to /sponsor/dashboard in middleware to preserve cookies
|
||||
// Handle /sponsor root redirect (preserves cookies)
|
||||
if (pathname === '/sponsor') {
|
||||
console.log(`[MIDDLEWARE] Redirecting /sponsor to /sponsor/dashboard`);
|
||||
logger.info('[MIDDLEWARE] Redirecting /sponsor → /sponsor/dashboard');
|
||||
return NextResponse.redirect(new URL('/sponsor/dashboard', request.url));
|
||||
}
|
||||
|
||||
@@ -29,118 +35,77 @@ export async function middleware(request: NextRequest) {
|
||||
const response = NextResponse.next();
|
||||
response.headers.set('x-pathname', pathname);
|
||||
|
||||
// Get session first (needed for all auth-related decisions)
|
||||
// Get session
|
||||
logger.info('[MIDDLEWARE] Fetching session...');
|
||||
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);
|
||||
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?returnTo=${encodeURIComponent(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;
|
||||
}
|
||||
|
||||
// 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`);
|
||||
// All checks passed
|
||||
logger.info('[MIDDLEWARE] ALLOWING ACCESS', { pathname });
|
||||
logger.info('[MIDDLEWARE] ========== REQUEST END (ALLOW) ==========');
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the home path for a specific role
|
||||
*/
|
||||
function getHomePathForRole(role: string): string {
|
||||
const roleHomeMap: Record<string, string> = {
|
||||
'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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user