diff --git a/src/components/moderation/ProfileManager.tsx b/src/components/moderation/ProfileManager.tsx index cdd5782a..585f86e4 100644 --- a/src/components/moderation/ProfileManager.tsx +++ b/src/components/moderation/ProfileManager.tsx @@ -19,6 +19,7 @@ interface UserProfile { id: string; user_id: string; username: string; + email?: string; display_name?: string; avatar_url?: string; banned: boolean; @@ -49,17 +50,15 @@ export function ProfileManager() { try { setLoading(true); - // Fetch profiles with user roles + // Fetch profiles with emails using secure RPC function const { data: profilesData, error: profilesError } = await supabase - .from('profiles') - .select('*') - .order('created_at', { ascending: false }); + .rpc('get_users_with_emails'); if (profilesError) throw profilesError; // Fetch roles for each user const profilesWithRoles = await Promise.all( - profilesData.map(async (profile) => { + (profilesData || []).map(async (profile) => { const { data: rolesData } = await supabase .from('user_roles') .select('role') @@ -453,7 +452,7 @@ export function ProfileManager() { targetUser={{ userId: deletionTarget.user_id, username: deletionTarget.username, - email: '', // Email not available in profile data + email: deletionTarget.email || 'Email not found', displayName: deletionTarget.display_name || undefined, roles: deletionTarget.roles }} diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 86074cb9..a532bf5e 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -4631,6 +4631,19 @@ export type Database = { Args: { _user_id: string } Returns: Json } + get_users_with_emails: { + Args: never + Returns: { + avatar_url: string + banned: boolean + created_at: string + display_name: string + email: string + id: string + user_id: string + username: string + }[] + } get_version_diff: { Args: { p_entity_type: string diff --git a/supabase/migrations/20251030180233_7cfbf422-43ea-4265-a85b-57aa207b4771.sql b/supabase/migrations/20251030180233_7cfbf422-43ea-4265-a85b-57aa207b4771.sql new file mode 100644 index 00000000..8411e9a7 --- /dev/null +++ b/supabase/migrations/20251030180233_7cfbf422-43ea-4265-a85b-57aa207b4771.sql @@ -0,0 +1,43 @@ +-- Create RPC function to get users with emails for admin/superuser +CREATE OR REPLACE FUNCTION public.get_users_with_emails() +RETURNS TABLE ( + id uuid, + user_id uuid, + username text, + email text, + display_name text, + avatar_url text, + banned boolean, + created_at timestamptz +) +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path TO 'public', 'auth' +AS $$ +BEGIN + -- Check if caller is superuser or admin + IF NOT EXISTS ( + SELECT 1 FROM public.user_roles + WHERE user_roles.user_id = auth.uid() + AND role IN ('superuser', 'admin') + ) THEN + RAISE EXCEPTION 'Access denied: requires admin or superuser role' + USING ERRCODE = '42501'; + END IF; + + -- Return profiles with emails from auth.users + RETURN QUERY + SELECT + p.id, + p.user_id, + p.username, + COALESCE(au.email, 'unknown@email.com') as email, + p.display_name, + p.avatar_url, + p.banned, + p.created_at + FROM public.profiles p + LEFT JOIN auth.users au ON au.id = p.user_id + ORDER BY p.created_at DESC; +END; +$$; \ No newline at end of file