Fix SECURITY DEFINER on filtered_profiles view

This commit is contained in:
gpt-engineer-app[bot]
2025-10-12 14:06:39 +00:00
parent 934c649514
commit 846c8ec7c0
2 changed files with 114 additions and 46 deletions

View File

@@ -2785,26 +2785,26 @@ export type Database = {
username: string | null username: string | null
} }
Insert: { Insert: {
avatar_image_id?: string | null avatar_image_id?: never
avatar_url?: string | null avatar_url?: never
banned?: boolean | null banned?: never
bio?: string | null bio?: never
coaster_count?: number | null coaster_count?: never
created_at?: string | null created_at?: string | null
date_of_birth?: string | null date_of_birth?: never
display_name?: string | null display_name?: string | null
home_park_id?: string | null home_park_id?: never
id?: string | null id?: string | null
location_id?: string | null location_id?: never
park_count?: number | null park_count?: never
personal_location?: string | null personal_location?: never
preferred_language?: string | null preferred_language?: string | null
preferred_pronouns?: string | null preferred_pronouns?: never
privacy_level?: string | null privacy_level?: string | null
reputation_score?: number | null reputation_score?: never
review_count?: number | null review_count?: never
ride_count?: number | null ride_count?: never
show_pronouns?: boolean | null show_pronouns?: never
theme_preference?: string | null theme_preference?: string | null
timezone?: string | null timezone?: string | null
updated_at?: string | null updated_at?: string | null
@@ -2812,48 +2812,33 @@ export type Database = {
username?: string | null username?: string | null
} }
Update: { Update: {
avatar_image_id?: string | null avatar_image_id?: never
avatar_url?: string | null avatar_url?: never
banned?: boolean | null banned?: never
bio?: string | null bio?: never
coaster_count?: number | null coaster_count?: never
created_at?: string | null created_at?: string | null
date_of_birth?: string | null date_of_birth?: never
display_name?: string | null display_name?: string | null
home_park_id?: string | null home_park_id?: never
id?: string | null id?: string | null
location_id?: string | null location_id?: never
park_count?: number | null park_count?: never
personal_location?: string | null personal_location?: never
preferred_language?: string | null preferred_language?: string | null
preferred_pronouns?: string | null preferred_pronouns?: never
privacy_level?: string | null privacy_level?: string | null
reputation_score?: number | null reputation_score?: never
review_count?: number | null review_count?: never
ride_count?: number | null ride_count?: never
show_pronouns?: boolean | null show_pronouns?: never
theme_preference?: string | null theme_preference?: string | null
timezone?: string | null timezone?: string | null
updated_at?: string | null updated_at?: string | null
user_id?: string | null user_id?: string | null
username?: string | null username?: string | null
} }
Relationships: [ 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"]
},
]
} }
moderation_sla_metrics: { moderation_sla_metrics: {
Row: { Row: {

View File

@@ -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.';