import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { verifyAdminToken, ADMIN_COOKIE_NAME } from '@/lib/session'; import { getTabForPathname, hasTabPermission } from '@/lib/permissions'; // Public endpoints and static resources const PUBLIC_PREFIXES = [ '/login', '/api/health', '/api/cron', '/api/external', '/_next', '/favicon.ico', ]; export async function middleware(request: NextRequest) { const { pathname, search } = request.nextUrl; // 1. Bypass static files and Next.js internal assets if ( pathname.startsWith('/_next') || pathname.includes('.') // any file with an extension (e.g. favicon.ico, images, robots.txt) ) { return NextResponse.next(); } // 2. Read session cookie const token = request.cookies.get(ADMIN_COOKIE_NAME)?.value; const session = await verifyAdminToken(token); const isLoginPage = pathname === '/login'; // 3. If accessing /login if (isLoginPage) { // If already logged in, redirect directly to dashboard if (session) { return NextResponse.redirect(new URL('/dashboard', request.url)); } return NextResponse.next(); } // 4. If accessing other public APIs (like cron or external integrations) const isPublicPrefix = PUBLIC_PREFIXES.some(prefix => pathname.startsWith(prefix)); if (isPublicPrefix) { return NextResponse.next(); } // 5. Protected route: redirect to /login if unauthenticated if (!session) { const loginUrl = new URL('/login', request.url); const returnUrl = pathname + (search || ''); if (pathname !== '/' && pathname !== '') { loginUrl.searchParams.set('from', returnUrl); } return NextResponse.redirect(loginUrl); } // 6. Check granular tab permission const tabKey = getTabForPathname(pathname); if (tabKey && !hasTabPermission(session.adminRole, session.permissions, tabKey)) { // If user lacks permission for this tab, redirect back to /dashboard const dashboardUrl = new URL('/dashboard', request.url); dashboardUrl.searchParams.set('unauthorized', tabKey); return NextResponse.redirect(dashboardUrl); } return NextResponse.next(); } export const config = { matcher: [ /* * Match all request paths except: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) */ '/((?!_next/static|_next/image|favicon.ico).*)', ], };