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
}
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: {

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