/** * Edge Function: revoke-user * Propósito: revocar acceso de un usuario (suspensión suave — cambia platform_role a 'guest') * Solo accesible por platform_admin. No elimina datos ni el usuario de auth.users. * Variables de entorno requeridas: SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SITE_URL * - SUPABASE_URL y SUPABASE_SERVICE_ROLE_KEY: disponibles automáticamente en Edge Functions * - SITE_URL: agregar manualmente en Supabase Dashboard → Edge Functions → Secrets * Valor producción: https://inq-roi.inqualityhq.com * Deploy: supabase functions deploy revoke-user */ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2' interface RevokeUserRequest { userId: string } Deno.serve(async (req: Request) => { const headers = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': Deno.env.get('SITE_URL') ?? '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', } // Manejar CORS preflight 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 }) } // Verificar platform_role desde DB — el JWT no es fuente confiable para el rol 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 revocar accesos' }), { status: 403, headers }, ) } // ── 2. Parsear y validar el body ────────────────────────────────────────── let body: RevokeUserRequest try { body = await req.json() as RevokeUserRequest } 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-revocación ─────────────────────────────────────────── if (userId === caller.id) { return new Response( JSON.stringify({ error: 'No puedes revocar tu propio acceso' }), { status: 400, headers }, ) } // ── 4. Cambiar platform_role a 'guest' ──────────────────────────────────── // Suspensión suave: el usuario queda en auth.users pero RLS bloquea acceso a datos. const { error: updateError } = await adminClient .from('users') .update({ platform_role: 'guest' }) .eq('id', userId) if (updateError) { return new Response( JSON.stringify({ error: `Error al revocar acceso: ${updateError.message}` }), { status: 500, headers }, ) } // ── 5. Revocar sesiones activas (opcional) ──────────────────────────────── // Intenta cerrar las sesiones activas del usuario. Si el método no está disponible // en esta versión del cliente, el rol 'guest' ya impide el acceso via RLS. try { await adminClient.auth.admin.signOut(userId) } catch { // No crítico: el rol ya fue cambiado. Las sesiones expirarán naturalmente. } return new Response( JSON.stringify({ success: true }), { status: 200, headers }, ) })