import { NextResponse } from 'next/server'; import { decideAccess } from '@/lib/auth/access'; import { auth } from '@/lib/auth'; /** * The single choke point for administrative access. * * Every /admin page and every /api/admin route passes through here, so no * individual page can forget to check. Pages get a redirect to the sign-in * screen; API routes get a status code, because a fetch that receives an HTML * login page is a confusing way to learn you are signed out. * * Write access is enforced here for /api/admin only, and deliberately NOT for * pages. A React server action POSTs to the URL of the page it lives on, so a * method check over /admin would refuse every form on the site to a read-only * account — including the sign-out button, which is not a write by any useful * definition. Page-level write protection belongs inside the actions * themselves, via lib/auth/actions.ts, where the intent is actually known. * * The device API (/api/setup, /api/display, /api/log) is deliberately outside * this matcher: the panel cannot sign in, and carries its own bearer token. */ export default auth((request) => { const { pathname } = request.nextUrl; const decision = decideAccess({ pathname, method: request.method, role: request.auth?.user?.role, }); if (decision.kind === 'redirect') { const target = new URL('/login', request.nextUrl.origin); target.searchParams.set('from', pathname); return NextResponse.redirect(target); } if (decision.kind === 'deny') { return NextResponse.json({ error: decision.error }, { status: decision.status }); } return NextResponse.next(); }); export const config = { matcher: ['/admin/:path*', '/api/admin/:path*'], };