fix(admin): fresh SSR reads, atomic user delete + sanitized errors, cookie-rotation in middleware, no-store on admin APIs
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user