Files
afrov2/admin/src/middleware.ts

81 lines
2.4 KiB
TypeScript

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