From 846c8ec7c0fbfdd3b5e1992858b8f2eb6f6d528f Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 12 Oct 2025 14:06:39 +0000 Subject: [PATCH] Fix SECURITY DEFINER on filtered_profiles view --- src/integrations/supabase/types.ts | 77 +++++++---------- ...6_f47fc137-13d2-4cfa-94d3-971f41b77258.sql | 83 +++++++++++++++++++ 2 files changed, 114 insertions(+), 46 deletions(-) create mode 100644 supabase/migrations/20251012140606_f47fc137-13d2-4cfa-94d3-971f41b77258.sql diff --git a/src/integrations/supabase/types.ts b/src/integrations/supabase/types.ts index 8ace6a56..d112510c 100644 --- a/src/integrations/supabase/types.ts +++ b/src/integrations/supabase/types.ts @@ -2785,26 +2785,26 @@ export type Database = { username: string | null } Insert: { - avatar_image_id?: string | null - avatar_url?: string | null - banned?: boolean | null - bio?: string | null - coaster_count?: number | null + avatar_image_id?: never + avatar_url?: never + banned?: never + bio?: never + coaster_count?: never created_at?: string | null - date_of_birth?: string | null + date_of_birth?: never display_name?: string | null - home_park_id?: string | null + home_park_id?: never id?: string | null - location_id?: string | null - park_count?: number | null - personal_location?: string | null + location_id?: never + park_count?: never + personal_location?: never preferred_language?: string | null - preferred_pronouns?: string | null + preferred_pronouns?: never privacy_level?: string | null - reputation_score?: number | null - review_count?: number | null - ride_count?: number | null - show_pronouns?: boolean | null + reputation_score?: never + review_count?: never + ride_count?: never + show_pronouns?: never theme_preference?: string | null timezone?: string | null updated_at?: string | null @@ -2812,48 +2812,33 @@ export type Database = { username?: string | null } Update: { - avatar_image_id?: string | null - avatar_url?: string | null - banned?: boolean | null - bio?: string | null - coaster_count?: number | null + avatar_image_id?: never + avatar_url?: never + banned?: never + bio?: never + coaster_count?: never created_at?: string | null - date_of_birth?: string | null + date_of_birth?: never display_name?: string | null - home_park_id?: string | null + home_park_id?: never id?: string | null - location_id?: string | null - park_count?: number | null - personal_location?: string | null + location_id?: never + park_count?: never + personal_location?: never preferred_language?: string | null - preferred_pronouns?: string | null + preferred_pronouns?: never privacy_level?: string | null - reputation_score?: number | null - review_count?: number | null - ride_count?: number | null - show_pronouns?: boolean | null + reputation_score?: never + review_count?: never + ride_count?: never + show_pronouns?: never theme_preference?: string | null timezone?: string | null updated_at?: string | null user_id?: string | null username?: string | null } - Relationships: [ - { - foreignKeyName: "profiles_home_park_id_fkey" - columns: ["home_park_id"] - isOneToOne: false - referencedRelation: "parks" - referencedColumns: ["id"] - }, - { - foreignKeyName: "profiles_location_id_fkey" - columns: ["location_id"] - isOneToOne: false - referencedRelation: "locations" - referencedColumns: ["id"] - }, - ] + Relationships: [] } moderation_sla_metrics: { Row: { diff --git a/supabase/migrations/20251012140606_f47fc137-13d2-4cfa-94d3-971f41b77258.sql b/supabase/migrations/20251012140606_f47fc137-13d2-4cfa-94d3-971f41b77258.sql new file mode 100644 index 00000000..6b6b5a63 --- /dev/null +++ b/supabase/migrations/20251012140606_f47fc137-13d2-4cfa-94d3-971f41b77258.sql @@ -0,0 +1,83 @@ +-- Drop and recreate filtered_profiles view without SECURITY DEFINER +-- This view applies field-level privacy using security definer functions +-- but the view itself should NOT be security definer +DROP VIEW IF EXISTS public.filtered_profiles; + +CREATE VIEW public.filtered_profiles AS +SELECT + p.id, + p.user_id, + p.username, + p.display_name, + p.privacy_level, + p.created_at, + p.updated_at, + -- Field-level privacy using security definer functions + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'bio') THEN p.bio + ELSE NULL + END AS bio, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'avatar_url') THEN p.avatar_url + ELSE NULL + END AS avatar_url, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'avatar_image_id') THEN p.avatar_image_id + ELSE NULL + END AS avatar_image_id, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'preferred_pronouns') THEN p.preferred_pronouns + ELSE NULL + END AS preferred_pronouns, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'preferred_pronouns') THEN p.show_pronouns + ELSE false + END AS show_pronouns, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'personal_location') THEN p.personal_location + ELSE NULL + END AS personal_location, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'location_id') THEN p.location_id + ELSE NULL + END AS location_id, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'home_park_id') THEN p.home_park_id + ELSE NULL + END AS home_park_id, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'date_of_birth') THEN p.date_of_birth + ELSE NULL + END AS date_of_birth, + p.timezone, + p.preferred_language, + p.theme_preference, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.ride_count + ELSE 0 + END AS ride_count, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.coaster_count + ELSE 0 + END AS coaster_count, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.park_count + ELSE 0 + END AS park_count, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.review_count + ELSE 0 + END AS review_count, + CASE + WHEN can_view_profile_field(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid), p.user_id, 'ride_count') THEN p.reputation_score + ELSE 0 + END AS reputation_score, + CASE + WHEN auth.uid() = p.user_id OR is_moderator(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid)) THEN p.banned + ELSE false + END AS banned +FROM public.profiles p +WHERE (NOT p.banned OR auth.uid() = p.user_id OR is_moderator(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid))) + AND (p.privacy_level = 'public' OR auth.uid() = p.user_id OR is_moderator(COALESCE(auth.uid(), '00000000-0000-0000-0000-000000000000'::uuid))); + +COMMENT ON VIEW public.filtered_profiles IS 'Profile view with field-level privacy controls. Uses security definer functions for permission checks but view itself respects querying user context.'; \ No newline at end of file