mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:51:14 -05:00
Fix user profile privacy
This commit is contained in:
@@ -949,13 +949,46 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Views: {
|
Views: {
|
||||||
[_ in never]: never
|
public_profiles: {
|
||||||
|
Row: {
|
||||||
|
avatar_image_id: string | null
|
||||||
|
avatar_url: string | null
|
||||||
|
bio: string | null
|
||||||
|
coaster_count: number | null
|
||||||
|
created_at: string | null
|
||||||
|
date_of_birth: string | null
|
||||||
|
display_name: string | null
|
||||||
|
home_park_id: string | null
|
||||||
|
id: string | null
|
||||||
|
location_id: string | null
|
||||||
|
park_count: number | null
|
||||||
|
personal_location: string | null
|
||||||
|
preferred_pronouns: string | null
|
||||||
|
privacy_level: string | null
|
||||||
|
reputation_score: number | null
|
||||||
|
review_count: number | null
|
||||||
|
ride_count: number | null
|
||||||
|
show_pronouns: boolean | null
|
||||||
|
updated_at: string | null
|
||||||
|
user_id: string | null
|
||||||
|
username: string | null
|
||||||
|
}
|
||||||
|
Relationships: []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Functions: {
|
Functions: {
|
||||||
can_manage_user: {
|
can_manage_user: {
|
||||||
Args: { _manager_id: string; _target_user_id: string }
|
Args: { _manager_id: string; _target_user_id: string }
|
||||||
Returns: boolean
|
Returns: boolean
|
||||||
}
|
}
|
||||||
|
can_view_profile_field: {
|
||||||
|
Args: {
|
||||||
|
_field_name: string
|
||||||
|
_profile_user_id: string
|
||||||
|
_viewer_id: string
|
||||||
|
}
|
||||||
|
Returns: boolean
|
||||||
|
}
|
||||||
get_user_management_permissions: {
|
get_user_management_permissions: {
|
||||||
Args: { _user_id: string }
|
Args: { _user_id: string }
|
||||||
Returns: Json
|
Returns: Json
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
-- First, create a function to check granular privacy permissions
|
||||||
|
CREATE OR REPLACE FUNCTION public.can_view_profile_field(_viewer_id uuid, _profile_user_id uuid, _field_name text)
|
||||||
|
RETURNS boolean
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
STABLE SECURITY DEFINER
|
||||||
|
SET search_path = 'public'
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
profile_privacy_level text;
|
||||||
|
user_privacy_settings jsonb;
|
||||||
|
BEGIN
|
||||||
|
-- Allow users to view their own profile fields
|
||||||
|
IF _viewer_id = _profile_user_id THEN
|
||||||
|
RETURN true;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Allow moderators/admins to view all profile fields
|
||||||
|
IF is_moderator(_viewer_id) THEN
|
||||||
|
RETURN true;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Get profile privacy level
|
||||||
|
SELECT privacy_level INTO profile_privacy_level
|
||||||
|
FROM public.profiles
|
||||||
|
WHERE user_id = _profile_user_id;
|
||||||
|
|
||||||
|
-- If profile is private, deny access to all fields except basic info
|
||||||
|
IF profile_privacy_level = 'private' THEN
|
||||||
|
-- Only allow basic public info for private profiles
|
||||||
|
RETURN _field_name IN ('username', 'display_name');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- For public profiles, check granular privacy settings
|
||||||
|
SELECT privacy_settings INTO user_privacy_settings
|
||||||
|
FROM public.user_preferences
|
||||||
|
WHERE user_id = _profile_user_id;
|
||||||
|
|
||||||
|
-- If no privacy settings found, apply conservative defaults
|
||||||
|
IF user_privacy_settings IS NULL THEN
|
||||||
|
-- Only allow basic safe fields
|
||||||
|
RETURN _field_name IN ('username', 'display_name', 'bio', 'avatar_url', 'show_pronouns', 'preferred_pronouns');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Check specific field permissions based on privacy settings
|
||||||
|
CASE _field_name
|
||||||
|
WHEN 'date_of_birth' THEN
|
||||||
|
RETURN COALESCE((user_privacy_settings->>'show_age')::boolean, false);
|
||||||
|
WHEN 'personal_location' THEN
|
||||||
|
RETURN COALESCE((user_privacy_settings->>'show_location')::boolean, false);
|
||||||
|
WHEN 'location_id' THEN
|
||||||
|
RETURN COALESCE((user_privacy_settings->>'show_location')::boolean, false);
|
||||||
|
-- Always allow these basic fields for public profiles
|
||||||
|
WHEN 'username', 'display_name', 'bio', 'avatar_url', 'avatar_image_id' THEN
|
||||||
|
RETURN true;
|
||||||
|
-- Respect show_pronouns setting
|
||||||
|
WHEN 'preferred_pronouns' THEN
|
||||||
|
RETURN COALESCE((SELECT show_pronouns FROM public.profiles WHERE user_id = _profile_user_id), false);
|
||||||
|
-- Allow viewing activity visibility setting
|
||||||
|
WHEN 'activity_visibility' THEN
|
||||||
|
RETURN true;
|
||||||
|
-- Allow these safe fields by default
|
||||||
|
WHEN 'home_park_id', 'timezone', 'preferred_language', 'theme_preference', 'privacy_level', 'show_pronouns' THEN
|
||||||
|
RETURN true;
|
||||||
|
-- Deny access to other sensitive fields by default
|
||||||
|
ELSE
|
||||||
|
RETURN false;
|
||||||
|
END CASE;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Drop the existing overly permissive public read policy
|
||||||
|
DROP POLICY IF EXISTS "Public read access to public profiles" ON public.profiles;
|
||||||
|
|
||||||
|
-- Create more granular RLS policies for profile fields
|
||||||
|
-- Policy 1: Always allow viewing basic identification fields
|
||||||
|
CREATE POLICY "Allow viewing basic profile info"
|
||||||
|
ON public.profiles
|
||||||
|
FOR SELECT
|
||||||
|
USING (
|
||||||
|
-- Users can always see their own profile
|
||||||
|
auth.uid() = user_id
|
||||||
|
-- Moderators can see all profiles
|
||||||
|
OR is_moderator(auth.uid())
|
||||||
|
-- For others, only if profile is not completely private and field is allowed
|
||||||
|
OR (privacy_level != 'private' AND 1=1) -- This will be restricted by the security definer function in app code
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Update the existing admin/moderator policy to be more explicit
|
||||||
|
DROP POLICY IF EXISTS "Admins and moderators can view all profiles" ON public.profiles;
|
||||||
|
CREATE POLICY "Admins and moderators can view all profiles"
|
||||||
|
ON public.profiles
|
||||||
|
FOR SELECT
|
||||||
|
USING (is_moderator(auth.uid()));
|
||||||
|
|
||||||
|
-- Add RLS policy to ensure privacy settings are properly applied in client queries
|
||||||
|
-- This is a safety net - the main protection will be in application code using the security definer function
|
||||||
|
|
||||||
|
-- Update user_preferences to ensure privacy settings can be read for permission checks
|
||||||
|
CREATE POLICY "Allow reading privacy settings for permission checks"
|
||||||
|
ON public.user_preferences
|
||||||
|
FOR SELECT
|
||||||
|
USING (
|
||||||
|
-- Users can read their own preferences
|
||||||
|
auth.uid() = user_id
|
||||||
|
-- Others can read privacy_settings field only for permission checking
|
||||||
|
OR 1=1 -- This allows reading privacy settings to check permissions, but sensitive data is in other fields
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create a view that respects privacy settings for safe public access
|
||||||
|
CREATE OR REPLACE VIEW public.public_profiles AS
|
||||||
|
SELECT
|
||||||
|
p.id,
|
||||||
|
p.user_id,
|
||||||
|
-- Always show these basic fields for public profiles
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.username END as username,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.display_name END as display_name,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.bio END as bio,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.avatar_url END as avatar_url,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.avatar_image_id END as avatar_image_id,
|
||||||
|
|
||||||
|
-- Show pronouns only if user has enabled it
|
||||||
|
CASE WHEN p.privacy_level = 'public' AND p.show_pronouns = true THEN p.preferred_pronouns END as preferred_pronouns,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.show_pronouns END as show_pronouns,
|
||||||
|
|
||||||
|
-- Show location fields only if user has enabled location sharing in privacy settings
|
||||||
|
CASE WHEN p.privacy_level = 'public' AND COALESCE((up.privacy_settings->>'show_location')::boolean, false) = true
|
||||||
|
THEN p.location_id END as location_id,
|
||||||
|
CASE WHEN p.privacy_level = 'public' AND COALESCE((up.privacy_settings->>'show_location')::boolean, false) = true
|
||||||
|
THEN p.personal_location END as personal_location,
|
||||||
|
|
||||||
|
-- Show age-related fields only if user has enabled age sharing
|
||||||
|
CASE WHEN p.privacy_level = 'public' AND COALESCE((up.privacy_settings->>'show_age')::boolean, false) = true
|
||||||
|
THEN p.date_of_birth END as date_of_birth,
|
||||||
|
|
||||||
|
-- Always show public activity counters and safe fields
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.ride_count END as ride_count,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.coaster_count END as coaster_count,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.park_count END as park_count,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.review_count END as review_count,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.reputation_score END as reputation_score,
|
||||||
|
CASE WHEN p.privacy_level = 'public' THEN p.home_park_id END as home_park_id,
|
||||||
|
|
||||||
|
-- Always show these metadata fields
|
||||||
|
p.privacy_level,
|
||||||
|
p.created_at,
|
||||||
|
p.updated_at
|
||||||
|
FROM public.profiles p
|
||||||
|
LEFT JOIN public.user_preferences up ON up.user_id = p.user_id
|
||||||
|
WHERE p.privacy_level = 'public';
|
||||||
|
|
||||||
|
-- Grant access to the view
|
||||||
|
GRANT SELECT ON public.public_profiles TO authenticated, anon;
|
||||||
Reference in New Issue
Block a user