Files
alva/supabase/functions/_shared/admin-auth.ts
T
gpt-engineer-app[bot] 60e7e43e94 Changes
Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com>
2026-07-11 14:00:26 +00:00

75 lines
2.2 KiB
TypeScript

import { createClient } from "https://esm.sh/@supabase/supabase-js@2.45.0";
/**
* Verifies the caller is an authenticated admin.
* Returns { ok: true } on success or { ok: false, response } with a
* ready-to-return Response on failure.
*/
export async function requireAdmin(
req: Request,
corsHeaders: Record<string, string>,
): Promise<{ ok: true; userId: string } | { ok: false; response: Response }> {
const authHeader = req.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
return {
ok: false,
response: new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}),
};
}
const supabaseUrl = Deno.env.get("SUPABASE_URL");
const anonKey = Deno.env.get("SUPABASE_ANON_KEY");
const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY");
if (!supabaseUrl || !anonKey || !serviceKey) {
return {
ok: false,
response: new Response(JSON.stringify({ error: "Server misconfigured" }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}),
};
}
const supabase = createClient(supabaseUrl, anonKey, {
global: { headers: { Authorization: authHeader } },
});
const token = authHeader.replace("Bearer ", "");
const { data, error } = await supabase.auth.getClaims(token);
if (error || !data?.claims?.sub) {
return {
ok: false,
response: new Response(JSON.stringify({ error: "Unauthorized" }), {
status: 401,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}),
};
}
const userId = data.claims.sub as string;
// Verify admin role via service-role client (bypasses RLS deterministically)
const admin = createClient(supabaseUrl, serviceKey);
const { data: role, error: roleErr } = await admin
.from("user_roles")
.select("role")
.eq("user_id", userId)
.eq("role", "admin")
.maybeSingle();
if (roleErr || !role) {
return {
ok: false,
response: new Response(JSON.stringify({ error: "Forbidden" }), {
status: 403,
headers: { ...corsHeaders, "Content-Type": "application/json" },
}),
};
}
return { ok: true, userId };
}