import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { getSessionCookie } from "better-auth/cookies"; // Routes that don't require authentication const publicRoutes = [ "/auth/sign-in", "/auth/verify", "/auth/2fa", "/auth/setup", "/api/auth", // External service callbacks and webhooks "/api/webhooks", "/api/kiosk", "/api/qbo/auth", "/api/qbo/disconnect", "/api/zabbix/webhook", // Health and status checks "/api/health", "/api/integrations/status", // Legal pages required by Intuit "/legal", // Mobile app API endpoints "/api/mobile", // OpenClaw external agent API (auth via x-openclaw-key header) "/api/openclaw", // LogLift evidence webhook (auth via x-openclaw-key header) "/api/rmm/loglift", // Sync endpoints called by scheduler "/api/sync", "/api/datto-rmm/sync", "/api/itglue/sync", "/api/veeam/sync", "/api/veeam/rpo-check", "/api/sentinelone/sync", "/api/engagement/sync", "/api/zoom/sync", "/api/qbo/sync", "/api/appgate/sync", "/api/reports/ticket-digest", "/api/notifications/morning-summary/send", // Duo Security sync and data endpoints "/api/duo", ]; // Routes that require admin or super-admin role const adminRoutes = ["/admin"]; export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl; // Allow public routes if (publicRoutes.some((route) => pathname.startsWith(route))) { return NextResponse.next(); } // Allow static files if ( pathname.startsWith("/_next") || pathname.startsWith("/favicon") || pathname.includes(".") ) { return NextResponse.next(); } // Check for session cookie const sessionCookie = getSessionCookie(request); if (!sessionCookie) { // Redirect to sign-in if no session const signInUrl = new URL("/auth/sign-in", request.url); signInUrl.searchParams.set("callbackUrl", pathname); return NextResponse.redirect(signInUrl); } // For admin routes, we need to verify the role // This is a basic check - the actual role verification happens in the API routes if (adminRoutes.some((route) => pathname.startsWith(route))) { // The session cookie exists, but we can't decode it here without the secret // Role-based access control is enforced at the API level // This middleware just ensures there's a session return NextResponse.next(); } return NextResponse.next(); } export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) */ "/((?!_next/static|_next/image|favicon.ico).*)", ], };