fix(admin): fresh SSR reads, atomic user delete + sanitized errors, cookie-rotation in middleware, no-store on admin APIs

This commit is contained in:
Gerhard Scheikl
2026-05-31 13:15:56 +02:00
parent 61bf6c013c
commit 535b2ef202
23 changed files with 180 additions and 95 deletions
+16 -4
View File
@@ -31,23 +31,35 @@ export async function middleware(request: NextRequest) {
// per-route requireAdmin()/requireAdminApi() checks.
const path = request.nextUrl.pathname;
if (path.startsWith('/admin') || path.startsWith('/api/admin')) {
// Carry any cookies Supabase rotated onto the working `response` over to a
// deny/redirect response, so a refreshed session/refresh token is always
// persisted — otherwise a fresh NextResponse would drop them and a
// concurrent request could spuriously 401.
const withCookies = (res: NextResponse): NextResponse => {
response.cookies.getAll().forEach((cookie) => res.cookies.set(cookie));
return res;
};
if (!user) {
if (path.startsWith('/api/admin')) {
return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
return withCookies(
NextResponse.json({ error: 'unauthorized' }, { status: 401 }),
);
}
const url = request.nextUrl.clone();
url.pathname = '/login';
url.search = '';
return NextResponse.redirect(url);
return withCookies(NextResponse.redirect(url));
}
if (user.app_metadata?.role !== 'admin') {
if (path.startsWith('/api/admin')) {
return NextResponse.json({ error: 'forbidden' }, { status: 403 });
return withCookies(
NextResponse.json({ error: 'forbidden' }, { status: 403 }),
);
}
const url = request.nextUrl.clone();
url.pathname = '/dashboard';
url.search = '';
return NextResponse.redirect(url);
return withCookies(NextResponse.redirect(url));
}
}