import { NextRequest, NextResponse } from 'next/server'; import { AuthMiddlewareConfig, MiddlewareRule } from './types'; export function createAuthMiddleware(config: AuthMiddlewareConfig) { const { loginUrl = '/sign-in', unauthorizedUrl = '/unauthorized', publicPaths = [], rules = [], onUnauthorized, onError, debug = false, } = config; return async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; try { // Check if path is public const isPublic = publicPaths.some(path => { if (typeof path === 'string') { return pathname === path || pathname.startsWith(path); } return path.test(pathname); }); if (isPublic) { return NextResponse.next(); } // Get auth token from cookie or header const token = request.cookies.get('auth-token')?.value || request.headers.get('authorization')?.replace('Bearer ', ''); if (!token) { if (debug) console.log('[Auth Middleware] No token found'); return redirectToLogin(request, loginUrl); } // Verify token and get user // This would typically call your backend or Firebase Admin SDK const user = await verifyToken(token); if (!user) { if (debug) console.log('[Auth Middleware] Invalid token'); return redirectToLogin(request, loginUrl); } // Check rules const applicableRule = findApplicableRule(pathname, rules); if (applicableRule) { const isAuthorized = await checkRule(user, applicableRule); if (!isAuthorized) { if (debug) console.log('[Auth Middleware] User not authorized for rule:', applicableRule); if (onUnauthorized) { const response = onUnauthorized(request, user); if (response) return response; } return redirectToUnauthorized(request, unauthorizedUrl); } } // Add user to headers for downstream use const requestHeaders = new Headers(request.headers); requestHeaders.set('x-user-id', user.uid); requestHeaders.set('x-user-role', user.role || ''); requestHeaders.set('x-user-department', user.department || ''); return NextResponse.next({ request: { headers: requestHeaders, }, }); } catch (error: any) { if (debug) console.error('[Auth Middleware] Error:', error); if (onError) { const response = onError(error, request); if (response) return response; } return redirectToLogin(request, loginUrl); } }; } function redirectToLogin(request: NextRequest, loginUrl: string) { const url = request.nextUrl.clone(); url.pathname = loginUrl; url.searchParams.set('returnUrl', request.nextUrl.pathname); return NextResponse.redirect(url); } function redirectToUnauthorized(request: NextRequest, unauthorizedUrl: string) { const url = request.nextUrl.clone(); url.pathname = unauthorizedUrl; return NextResponse.redirect(url); } function findApplicableRule(pathname: string, rules: MiddlewareRule[]): MiddlewareRule | null { return rules.find(rule => { if (!rule.path) return true; // Rule applies to all paths const paths = Array.isArray(rule.path) ? rule.path : [rule.path]; return paths.some(path => { if (typeof path === 'string') { return pathname === path || pathname.startsWith(path); } return path.test(pathname); }); }) || null; } async function checkRule(user: any, rule: MiddlewareRule): Promise { // Custom check if (rule.custom) { const result = await rule.custom(user); if (!result) return false; } // Admin checks if (rule.isSuperAdmin && !user.isSuperAdmin) return false; if (rule.isAdmin && !user.isAdmin && !user.isSuperAdmin) return false; // Role checks const requiredRoles = rule.roles || rule.role; if (requiredRoles) { const roles = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles]; const userRoles = [...(user.roles || []), ...(user.role ? [user.role] : [])]; const hasRole = rule.requireAll ? roles.every(r => userRoles.includes(r)) : roles.some(r => userRoles.includes(r)); if (!hasRole) return false; } // Department checks const requiredDepts = rule.departments || rule.department; if (requiredDepts) { const depts = Array.isArray(requiredDepts) ? requiredDepts : [requiredDepts]; const userDepts = [...(user.departments || []), ...(user.department ? [user.department] : [])]; const hasDept = rule.requireAll ? depts.every(d => userDepts.includes(d)) : depts.some(d => userDepts.includes(d)); if (!hasDept) return false; } // Permission checks const requiredPerms = rule.permissions || rule.permission; if (requiredPerms) { const perms = Array.isArray(requiredPerms) ? requiredPerms : [requiredPerms]; const userPerms = user.permissions || []; const hasPerm = rule.requireAll ? perms.every(p => userPerms.includes(p)) : perms.some(p => userPerms.includes(p)); if (!hasPerm) return false; } return true; } // Mock token verification - replace with actual implementation async function verifyToken(token: string): Promise { // This would typically use Firebase Admin SDK or your backend API // For now, return a mock user return { uid: 'user123', email: 'user@example.com', role: 'editor', department: 'marketing', permissions: ['edit:content', 'view:analytics'], }; }