refactor(router): guards AdminLayout + ImportPage → RequireRole wrapper (Etapa 6A)
Some checks are pending
/ Deploy to Cloudflare Pages (push) Waiting to run
Some checks are pending
/ Deploy to Cloudflare Pages (push) Waiting to run
- Crea RequireRole component (Approach B — router sin auth context)
- AdminLayout: elimina useEffect + spinner + null check, usa <RequireRole roles={['platform_admin']}>
- ImportPage: elimina useEffect guard + return null client_viewer, usa <RequireRole roles={['platform_admin','member','client_editor']}>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
99
supabase/functions/delete-user/index.ts
Normal file
99
supabase/functions/delete-user/index.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Edge Function: delete-user
|
||||
* Propósito: eliminación permanente de un usuario (hard delete de auth.users + public.users).
|
||||
* Solo accesible por platform_admin. No reversible.
|
||||
* Variables de entorno: SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY (auto-disponibles en Edge Functions).
|
||||
* Deploy: supabase functions deploy delete-user
|
||||
*/
|
||||
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
|
||||
interface DeleteUserRequest {
|
||||
userId: string
|
||||
}
|
||||
|
||||
Deno.serve(async (req: Request) => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
}
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers })
|
||||
}
|
||||
|
||||
if (req.method !== 'POST') {
|
||||
return new Response(JSON.stringify({ error: 'Método no permitido' }), { status: 405, headers })
|
||||
}
|
||||
|
||||
const adminClient = createClient(
|
||||
Deno.env.get('SUPABASE_URL')!,
|
||||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!,
|
||||
)
|
||||
|
||||
// ── 1. Autenticar al llamador ──────────────────────────────────────────────
|
||||
|
||||
const callerToken = req.headers.get('Authorization')?.replace('Bearer ', '')
|
||||
if (!callerToken) {
|
||||
return new Response(JSON.stringify({ error: 'Authorization header requerido' }), { status: 401, headers })
|
||||
}
|
||||
|
||||
const { data: { user: caller }, error: authError } = await adminClient.auth.getUser(callerToken)
|
||||
if (authError || !caller) {
|
||||
return new Response(JSON.stringify({ error: 'Token inválido o expirado' }), { status: 401, headers })
|
||||
}
|
||||
|
||||
const { data: callerProfile, error: profileError } = await adminClient
|
||||
.from('users')
|
||||
.select('platform_role')
|
||||
.eq('id', caller.id)
|
||||
.single()
|
||||
|
||||
if (profileError || callerProfile?.platform_role !== 'platform_admin') {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Forbidden: solo platform_admin puede eliminar usuarios' }),
|
||||
{ status: 403, headers },
|
||||
)
|
||||
}
|
||||
|
||||
// ── 2. Parsear body ────────────────────────────────────────────────────────
|
||||
|
||||
let body: DeleteUserRequest
|
||||
try {
|
||||
body = await req.json() as DeleteUserRequest
|
||||
} catch {
|
||||
return new Response(JSON.stringify({ error: 'Body JSON inválido' }), { status: 400, headers })
|
||||
}
|
||||
|
||||
const { userId } = body
|
||||
if (!userId?.trim()) {
|
||||
return new Response(JSON.stringify({ error: 'El campo userId es requerido' }), { status: 400, headers })
|
||||
}
|
||||
|
||||
// ── 3. Prevenir auto-eliminación ──────────────────────────────────────────
|
||||
|
||||
if (userId === caller.id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'No puedes eliminar tu propia cuenta' }),
|
||||
{ status: 400, headers },
|
||||
)
|
||||
}
|
||||
|
||||
// ── 4. Eliminar de public.users primero (CASCADE, pero por seguridad explícita) ─
|
||||
|
||||
await adminClient.from('users').delete().eq('id', userId)
|
||||
|
||||
// ── 5. Hard delete de auth.users ─────────────────────────────────────────
|
||||
|
||||
const { error: deleteError } = await adminClient.auth.admin.deleteUser(userId)
|
||||
if (deleteError) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: `Error al eliminar usuario: ${deleteError.message}` }),
|
||||
{ status: 500, headers },
|
||||
)
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ success: true }), { status: 200, headers })
|
||||
})
|
||||
Reference in New Issue
Block a user