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
+13
View File
@@ -0,0 +1,13 @@
import { NextResponse } from 'next/server';
/**
* Wrapper around NextResponse.json that marks the response uncacheable. All
* admin API responses must never be stored by browsers, proxies, or Next's
* own caches, since they reflect privileged, frequently-changing state.
*/
export function jsonNoStore(body: unknown, init?: ResponseInit): NextResponse {
const res = NextResponse.json(body, init);
res.headers.set('Cache-Control', 'no-store');
res.headers.set('Pragma', 'no-cache');
return res;
}
+4 -3
View File
@@ -1,7 +1,8 @@
import { redirect } from 'next/navigation';
import { NextResponse } from 'next/server';
import type { NextResponse } from 'next/server';
import type { User } from '@supabase/supabase-js';
import { createSupabaseServerClient } from '@/lib/supabase/server';
import { jsonNoStore } from '@/lib/admin/response';
export function isAdmin(user: User | null | undefined): boolean {
return user?.app_metadata?.role === 'admin';
@@ -35,13 +36,13 @@ export async function requireAdminApi(): Promise<
if (!user) {
return {
ok: false,
response: NextResponse.json({ error: 'unauthorized' }, { status: 401 }),
response: jsonNoStore({ error: 'unauthorized' }, { status: 401 }),
};
}
if (!isAdmin(user)) {
return {
ok: false,
response: NextResponse.json({ error: 'forbidden' }, { status: 403 }),
response: jsonNoStore({ error: 'forbidden' }, { status: 403 }),
};
}
return { ok: true, user };
+7
View File
@@ -11,6 +11,13 @@ export function getSupabaseAdmin(): SupabaseClient {
}
_admin = createClient(url, key, {
auth: { autoRefreshToken: false, persistSession: false },
global: {
// Force every request the admin client makes (GoTrue listUsers and
// PostgREST reads alike) to bypass Next's fetch Data Cache, so the admin
// surface always reflects current state regardless of page config.
fetch: (input: RequestInfo | URL, init?: RequestInit) =>
fetch(input, { ...init, cache: 'no-store' }),
},
});
return _admin;
}