diff --git a/src/components/profile/LocationDisplay.tsx b/src/components/profile/LocationDisplay.tsx index ba4b2f87..bb36262b 100644 --- a/src/components/profile/LocationDisplay.tsx +++ b/src/components/profile/LocationDisplay.tsx @@ -19,25 +19,26 @@ export function LocationDisplay({ location, userId, isOwnProfile }: LocationDisp }, [userId, isOwnProfile]); const fetchLocationPrivacy = async () => { - // Always show location for own profile - if (isOwnProfile) { - setShowLocation(true); - return; - } - try { - const { data } = await supabase - .from('user_preferences') - .select('privacy_settings') - .eq('user_id', userId) - .maybeSingle(); + const { data: { user } } = await supabase.auth.getUser(); + const viewerId = user?.id; - if (data?.privacy_settings) { - const settings = data.privacy_settings as any; - setShowLocation(settings.show_location || false); + // Use the secure function to check location visibility + const { data, error } = await supabase + .rpc('can_view_user_location', { + _viewer_id: viewerId, + _profile_user_id: userId + }); + + if (error) { + console.error('Error checking location privacy:', error); + setShowLocation(false); + return; } + + setShowLocation(data || false); } catch (error) { - console.error('Error fetching location privacy:', error); + console.error('Error checking location privacy:', error); setShowLocation(false); } }; diff --git a/src/components/profile/PersonalLocationDisplay.tsx b/src/components/profile/PersonalLocationDisplay.tsx index 2a1467ea..67354037 100644 --- a/src/components/profile/PersonalLocationDisplay.tsx +++ b/src/components/profile/PersonalLocationDisplay.tsx @@ -16,23 +16,24 @@ export function PersonalLocationDisplay({ personalLocation, userId, isOwnProfile }, [userId, isOwnProfile]); const fetchLocationPrivacy = async () => { - // Always show location for own profile - if (isOwnProfile) { - setShowLocation(true); - return; - } - try { - const { data } = await supabase - .from('user_preferences') - .select('privacy_settings') - .eq('user_id', userId) - .maybeSingle(); + const { data: { user } } = await supabase.auth.getUser(); + const viewerId = user?.id; - if (data?.privacy_settings) { - const settings = data.privacy_settings as any; - setShowLocation(settings.show_location || false); + // Use the secure function to check location visibility + const { data, error } = await supabase + .rpc('can_view_user_location', { + _viewer_id: viewerId, + _profile_user_id: userId + }); + + if (error) { + console.error('Error checking location privacy:', error); + setShowLocation(false); + return; } + + setShowLocation(data || false); } catch (error) { console.error('Error fetching location privacy:', error); setShowLocation(false); diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index bc01c457..fa5d4b5f 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -964,6 +964,10 @@ export type Database = { } Returns: boolean } + can_view_user_location: { + Args: { _profile_user_id: string; _viewer_id: string } + Returns: boolean + } get_user_management_permissions: { Args: { _user_id: string } Returns: Json diff --git a/supabase/migrations/20250929221847_f44d59c9-f44e-46e4-959d-28281d563718.sql b/supabase/migrations/20250929221847_f44d59c9-f44e-46e4-959d-28281d563718.sql new file mode 100644 index 00000000..8c01383d --- /dev/null +++ b/supabase/migrations/20250929221847_f44d59c9-f44e-46e4-959d-28281d563718.sql @@ -0,0 +1,53 @@ +-- Fix user_preferences privacy exposure +-- Drop the overly permissive policy +DROP POLICY IF EXISTS "Allow reading privacy settings for permission checks" ON public.user_preferences; + +-- Create a restricted policy: users can only read their own preferences +CREATE POLICY "Users can read their own preferences" +ON public.user_preferences +FOR SELECT +TO authenticated +USING (auth.uid() = user_id); + +-- Create a policy for moderators to read all preferences +CREATE POLICY "Moderators can read all preferences" +ON public.user_preferences +FOR SELECT +TO authenticated +USING (is_moderator(auth.uid())); + +-- Create a security definer function to check if a user's location should be visible +CREATE OR REPLACE FUNCTION public.can_view_user_location(_viewer_id uuid, _profile_user_id uuid) +RETURNS boolean +LANGUAGE plpgsql +STABLE +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + user_privacy_settings jsonb; +BEGIN + -- Always allow users to view their own location + IF _viewer_id = _profile_user_id THEN + RETURN true; + END IF; + + -- Allow moderators to view all locations + IF is_moderator(_viewer_id) THEN + RETURN true; + END IF; + + -- Check the user's privacy settings + SELECT privacy_settings INTO user_privacy_settings + FROM public.user_preferences + WHERE user_id = _profile_user_id; + + -- If no privacy settings found, default to not showing location + IF user_privacy_settings IS NULL THEN + RETURN false; + END IF; + + -- Check if the user has enabled location visibility + RETURN COALESCE((user_privacy_settings->>'show_location')::boolean, false); +END; +$$; \ No newline at end of file