diff --git a/supabase/functions/delete-admin-user/index.ts b/supabase/functions/delete-admin-user/index.ts new file mode 100644 index 0000000..48a5a55 --- /dev/null +++ b/supabase/functions/delete-admin-user/index.ts @@ -0,0 +1,65 @@ +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +Deno.serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + const authHeader = req.headers.get('Authorization'); + if (!authHeader) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + const supabaseClient = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_ANON_KEY')!, + { global: { headers: { Authorization: authHeader } } }, + ); + const { data: { user }, error: userError } = await supabaseClient.auth.getUser(); + if (userError || !user) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + const body = await req.json().catch(() => ({})); + const { userId } = body; + if (!userId || typeof userId !== 'string') { + return new Response(JSON.stringify({ error: 'userId is required' }), { + status: 400, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + if (userId === user.id) { + return new Response(JSON.stringify({ error: 'Cannot delete your own account' }), { + status: 400, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + const supabaseAdmin = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + ); + const { error } = await supabaseAdmin.auth.admin.deleteUser(userId); + if (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + return new Response(JSON.stringify({ success: true }), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); +}); diff --git a/supabase/functions/invite-admin-user/index.ts b/supabase/functions/invite-admin-user/index.ts new file mode 100644 index 0000000..fd6fbcd --- /dev/null +++ b/supabase/functions/invite-admin-user/index.ts @@ -0,0 +1,59 @@ +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +Deno.serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + const authHeader = req.headers.get('Authorization'); + if (!authHeader) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + const supabaseClient = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_ANON_KEY')!, + { global: { headers: { Authorization: authHeader } } }, + ); + const { data: { user }, error: userError } = await supabaseClient.auth.getUser(); + if (userError || !user) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + const body = await req.json().catch(() => ({})); + const { email } = body; + if (!email || typeof email !== 'string') { + return new Response(JSON.stringify({ error: 'email is required' }), { + status: 400, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + const supabaseAdmin = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + ); + const { data, error } = await supabaseAdmin.auth.admin.inviteUserByEmail(email); + if (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + return new Response( + JSON.stringify({ id: data.user.id, email: data.user.email }), + { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }, + ); +}); diff --git a/supabase/functions/list-admin-users/index.ts b/supabase/functions/list-admin-users/index.ts new file mode 100644 index 0000000..1be50a6 --- /dev/null +++ b/supabase/functions/list-admin-users/index.ts @@ -0,0 +1,55 @@ +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', +}; + +Deno.serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + const authHeader = req.headers.get('Authorization'); + if (!authHeader) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + // Verify the caller is a valid authenticated user + const supabaseClient = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_ANON_KEY')!, + { global: { headers: { Authorization: authHeader } } }, + ); + const { data: { user }, error: userError } = await supabaseClient.auth.getUser(); + if (userError || !user) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + // Use service role to list all users + const supabaseAdmin = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + ); + const { data: { users }, error } = await supabaseAdmin.auth.admin.listUsers(); + if (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); + } + + const result = users.map(({ id, email, created_at, last_sign_in_at }) => ({ + id, email, created_at, last_sign_in_at, + })); + + return new Response(JSON.stringify(result), { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + }); +});