From cd3d9ae34f6fc5f0fcc88b7904eacf4bf34256ae Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sun, 4 Jan 2026 23:02:28 +0100 Subject: [PATCH] middleware fix wip --- adapters/bootstrap/SeedDemoUsers.ts | 4 ++-- apps/website/app/admin/layout.tsx | 4 ++++ .../errors/EnhancedErrorBoundary.tsx | 24 +++++++++++++++++++ apps/website/lib/auth/AuthFlowRouter.ts | 9 ++++--- apps/website/lib/auth/RouteAccessPolicy.ts | 4 +++- apps/website/lib/auth/RouteGuard.ts | 15 ++++++++++++ apps/website/lib/gateways/SessionGateway.ts | 3 ++- tests/e2e/website/website-pages.test.ts | 24 ++++++++++++++----- 8 files changed, 74 insertions(+), 13 deletions(-) diff --git a/adapters/bootstrap/SeedDemoUsers.ts b/adapters/bootstrap/SeedDemoUsers.ts index 952aa1dbe..f2a89e83c 100644 --- a/adapters/bootstrap/SeedDemoUsers.ts +++ b/adapters/bootstrap/SeedDemoUsers.ts @@ -38,9 +38,9 @@ export class SeedDemoUsers { { email: 'demo.sponsor@example.com', password: 'Demo1234!', - needsAdminUser: false, + needsAdminUser: true, needsPrimaryDriverId: false, - roles: ['user'], + roles: ['sponsor'], displayName: 'Jane Sponsor', }, { diff --git a/apps/website/app/admin/layout.tsx b/apps/website/app/admin/layout.tsx index 5c1bf47c9..1621af2d8 100644 --- a/apps/website/app/admin/layout.tsx +++ b/apps/website/app/admin/layout.tsx @@ -12,11 +12,15 @@ interface AdminLayoutProps { * Uses RouteGuard to enforce access control server-side. */ export default async function AdminLayout({ children }: AdminLayoutProps) { + console.log('[ADMIN LAYOUT] ========== ADMIN LAYOUT CALLED =========='); const headerStore = await headers(); const pathname = headerStore.get('x-pathname') || '/'; + console.log('[ADMIN LAYOUT] Pathname:', pathname); const guard = createRouteGuard(); + console.log('[ADMIN LAYOUT] About to call guard.enforce'); await guard.enforce({ pathname }); + console.log('[ADMIN LAYOUT] guard.enforce completed successfully'); return (
diff --git a/apps/website/components/errors/EnhancedErrorBoundary.tsx b/apps/website/components/errors/EnhancedErrorBoundary.tsx index a3c2c4dd0..4fb793533 100644 --- a/apps/website/components/errors/EnhancedErrorBoundary.tsx +++ b/apps/website/components/errors/EnhancedErrorBoundary.tsx @@ -47,6 +47,18 @@ export class EnhancedErrorBoundary extends Component { } static getDerivedStateFromError(error: Error): State { + // Don't catch Next.js navigation errors (redirect, notFound, etc.) + if (error && typeof error === 'object' && 'digest' in error) { + const digest = (error as any).digest; + if (typeof digest === 'string' && ( + digest.startsWith('NEXT_REDIRECT') || + digest.startsWith('NEXT_NOT_FOUND') + )) { + // Re-throw Next.js navigation errors so they can be handled properly + throw error; + } + } + return { hasError: true, error, @@ -56,6 +68,18 @@ export class EnhancedErrorBoundary extends Component { } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + // Don't catch Next.js navigation errors (redirect, notFound, etc.) + if (error && typeof error === 'object' && 'digest' in error) { + const digest = (error as any).digest; + if (typeof digest === 'string' && ( + digest.startsWith('NEXT_REDIRECT') || + digest.startsWith('NEXT_NOT_FOUND') + )) { + // Re-throw Next.js navigation errors so they can be handled properly + throw error; + } + } + // Add to React error history const reactErrors = (window as any).__GRIDPILOT_REACT_ERRORS__ || []; reactErrors.push({ diff --git a/apps/website/lib/auth/AuthFlowRouter.ts b/apps/website/lib/auth/AuthFlowRouter.ts index 24e36417a..f5f50131b 100644 --- a/apps/website/lib/auth/AuthFlowRouter.ts +++ b/apps/website/lib/auth/AuthFlowRouter.ts @@ -227,8 +227,11 @@ export function handleAuthFlow( return { shouldRedirect: false, shouldShowPage: true }; case AuthActionType.SHOW_PERMISSION_ERROR: - const errorUrl = router.getLoginRedirectUrl(); - logger.info('[handleAuthFlow] Returning SHOW_PERMISSION_ERROR', { errorUrl }); - return { shouldRedirect: true, redirectUrl: errorUrl }; + // Redirect to user's home page instead of login (they're already logged in) + const homeUrl = session?.role === 'sponsor' ? routes.sponsor.dashboard : + session?.role === 'admin' ? routes.admin.root : + routes.protected.dashboard; + logger.info('[handleAuthFlow] Returning SHOW_PERMISSION_ERROR, redirecting to home', { homeUrl, userRole: session?.role }); + return { shouldRedirect: true, redirectUrl: homeUrl }; } } \ No newline at end of file diff --git a/apps/website/lib/auth/RouteAccessPolicy.ts b/apps/website/lib/auth/RouteAccessPolicy.ts index 9b467d057..c002c920b 100644 --- a/apps/website/lib/auth/RouteAccessPolicy.ts +++ b/apps/website/lib/auth/RouteAccessPolicy.ts @@ -49,7 +49,9 @@ export class RouteAccessPolicy { */ requiredRoles(logicalPathname: string): string[] | null { // Use catalog's role-based access method - return this.catalog.getRequiredRoles(logicalPathname); + const roles = this.catalog.getRequiredRoles(logicalPathname); + console.log(`[RouteAccessPolicy] requiredRoles for ${logicalPathname}:`, roles); + return roles; } /** diff --git a/apps/website/lib/auth/RouteGuard.ts b/apps/website/lib/auth/RouteGuard.ts index 30530de10..c9cbf7bc2 100644 --- a/apps/website/lib/auth/RouteGuard.ts +++ b/apps/website/lib/auth/RouteGuard.ts @@ -4,6 +4,9 @@ import { RouteAccessPolicy } from './RouteAccessPolicy'; import { SessionGateway } from '../gateways/SessionGateway'; import { AuthRedirectBuilder } from './AuthRedirectBuilder'; import type { AuthSessionDTO } from '../types/generated/AuthSessionDTO'; +import { ConsoleLogger } from '../infrastructure/logging/ConsoleLogger'; + +const logger = new ConsoleLogger(); export class RouteGuard { constructor( @@ -14,43 +17,55 @@ export class RouteGuard { ) {} async enforce({ pathname }: { pathname: string }): Promise { + logger.info('[RouteGuard] enforce called', { pathname }); + // Step 1: Interpret the pathname const { logicalPathname } = this.interpreter.interpret(pathname); + logger.info('[RouteGuard] logicalPathname', { logicalPathname }); // Step 2: Check if public non-auth page if (this.policy.isPublic(logicalPathname) && !this.policy.isAuthPage(logicalPathname)) { + logger.info('[RouteGuard] Public non-auth page, allowing access'); return; // Allow access } // Step 3: Handle auth pages if (this.policy.isAuthPage(logicalPathname)) { + logger.info('[RouteGuard] Auth page detected'); const session = await this.gateway.getSession(); if (session) { // User is logged in, redirect away from auth page const redirectPath = this.builder.awayFromAuthPage({ session, currentPathname: pathname }); + logger.info('[RouteGuard] Redirecting away from auth page', { redirectPath }); redirect(redirectPath); } // No session, allow access to auth page + logger.info('[RouteGuard] No session, allowing access to auth page'); return; } // Step 4: Handle protected pages const session = await this.gateway.getSession(); + logger.info('[RouteGuard] Protected page, session', { hasSession: !!session, role: session?.user?.role, sessionData: JSON.stringify(session, null, 2) }); // No session, redirect to login if (!session) { const loginPath = this.builder.toLogin({ currentPathname: pathname }); + logger.info('[RouteGuard] No session, redirecting to login', { loginPath }); redirect(loginPath); } // Check required roles const reqRoles = this.policy.requiredRoles(logicalPathname); + logger.info('[RouteGuard] Checking required roles', { reqRoles, userRole: session.user?.role }); if (reqRoles && session.user?.role && !reqRoles.includes(session.user.role)) { const loginPath = this.builder.toLogin({ currentPathname: pathname }); + logger.info('[RouteGuard] Role mismatch, redirecting to login', { loginPath, reqRoles, userRole: session.user.role }); redirect(loginPath); } // All checks passed, allow access + logger.info('[RouteGuard] All checks passed, allowing access'); return; } } diff --git a/apps/website/lib/gateways/SessionGateway.ts b/apps/website/lib/gateways/SessionGateway.ts index c40692555..ea2d42228 100644 --- a/apps/website/lib/gateways/SessionGateway.ts +++ b/apps/website/lib/gateways/SessionGateway.ts @@ -78,7 +78,8 @@ export class SessionGateway { // Parse and return session data const session = await response.json(); - console.log(`[SESSION] Session parsed successfully`); + console.log(`[SESSION] Session parsed successfully:`, JSON.stringify(session, null, 2)); + console.log(`[SESSION] Session user role:`, session?.user?.role); return session as AuthSessionDTO; } catch (error) { console.log(`[SESSION] Error occurred:`, error); diff --git a/tests/e2e/website/website-pages.test.ts b/tests/e2e/website/website-pages.test.ts index 740471f3d..460becbc6 100644 --- a/tests/e2e/website/website-pages.test.ts +++ b/tests/e2e/website/website-pages.test.ts @@ -32,6 +32,13 @@ test.describe('Website Pages - TypeORM Integration', () => { for (const route of publicRoutes) { const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params); const response = await page.goto(`${WEBSITE_BASE_URL}${path}`); + const status = response?.status(); + const finalUrl = page.url(); + + console.log(`[TEST DEBUG] Public route - Path: ${path}, Status: ${status}, Final URL: ${finalUrl}`); + if (status === 500) { + console.log(`[TEST DEBUG] 500 error on ${path} - Page title: ${await page.title()}`); + } // Should load successfully or show 404 page expect(response?.ok() || response?.status() === 404).toBeTruthy(); @@ -59,11 +66,16 @@ test.describe('Website Pages - TypeORM Integration', () => { for (const route of adminRoutes) { const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params); - // Regular auth user should be blocked + // Regular auth user should be redirected to their home page (dashboard) { const auth = await WebsiteAuthManager.createAuthContext(browser, request, 'auth'); - await auth.page.goto(`${WEBSITE_BASE_URL}${path}`); - expect(auth.page.url().includes('login')).toBeTruthy(); + const response = await auth.page.goto(`${WEBSITE_BASE_URL}${path}`); + const finalUrl = auth.page.url(); + console.log(`[TEST DEBUG] Admin route test - Path: ${path}`); + console.log(`[TEST DEBUG] Response status: ${response?.status()}`); + console.log(`[TEST DEBUG] Final URL: ${finalUrl}`); + console.log(`[TEST DEBUG] Page title: ${await auth.page.title()}`); + expect(auth.page.url().includes('dashboard')).toBeTruthy(); await auth.context.close(); } @@ -84,14 +96,14 @@ test.describe('Website Pages - TypeORM Integration', () => { for (const route of sponsorRoutes) { const path = routeManager.resolvePathTemplate(route.pathTemplate, route.params); - // Regular auth user should be blocked + // Regular auth user should be redirected to their home page (dashboard) { const auth = await WebsiteAuthManager.createAuthContext(browser, request, 'auth'); await auth.page.goto(`${WEBSITE_BASE_URL}${path}`); const finalUrl = auth.page.url(); console.log(`[DEBUG] Final URL: ${finalUrl}`); - console.log(`[DEBUG] Includes 'login': ${finalUrl.includes('login')}`); - expect(finalUrl.includes('login')).toBeTruthy(); + console.log(`[DEBUG] Includes 'dashboard': ${finalUrl.includes('dashboard')}`); + expect(finalUrl.includes('dashboard')).toBeTruthy(); await auth.context.close(); }