import { createServerClient } from '@supabase/ssr'; import { NextResponse, type NextRequest } from 'next/server'; export async function middleware(request: NextRequest) { let response = NextResponse.next({ request }); const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: { getAll() { return request.cookies.getAll(); }, setAll(toSet) { toSet.forEach(({ name, value }) => request.cookies.set(name, value)); response = NextResponse.next({ request }); toSet.forEach(({ name, value, options }) => response.cookies.set(name, value, options), ); }, }, }, ); const { data: { user }, } = await supabase.auth.getUser(); // Defense-in-depth: guard the admin surface here in addition to the // per-route requireAdmin()/requireAdminApi() checks. const path = request.nextUrl.pathname; if (path.startsWith('/admin') || path.startsWith('/api/admin')) { if (!user) { if (path.startsWith('/api/admin')) { return NextResponse.json({ error: 'unauthorized' }, { status: 401 }); } const url = request.nextUrl.clone(); url.pathname = '/login'; url.search = ''; return NextResponse.redirect(url); } if (user.app_metadata?.role !== 'admin') { if (path.startsWith('/api/admin')) { return NextResponse.json({ error: 'forbidden' }, { status: 403 }); } const url = request.nextUrl.clone(); url.pathname = '/dashboard'; url.search = ''; return NextResponse.redirect(url); } } return response; } export const config = { matcher: [ '/((?!_next/static|_next/image|favicon.ico|api/tunnel/claim).*)', '/admin/:path*', '/api/admin/:path*', ], };