mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 04:51:11 -05:00
159 lines
5.8 KiB
SQL
159 lines
5.8 KiB
SQL
-- Fix Security Definer Views Issue
|
|
-- Add explicit security_invoker = true to views that are missing it
|
|
-- This ensures views execute with the querying user's permissions, not the creator's
|
|
|
|
-- Fix moderation_queue_with_entities view
|
|
DROP VIEW IF EXISTS public.moderation_queue_with_entities CASCADE;
|
|
|
|
CREATE VIEW public.moderation_queue_with_entities
|
|
WITH (security_invoker = true)
|
|
AS
|
|
SELECT
|
|
cs.id,
|
|
cs.submission_type,
|
|
cs.status,
|
|
|
|
-- Temporal fields (with backward compatibility alias)
|
|
cs.submitted_at AS created_at,
|
|
cs.submitted_at,
|
|
cs.reviewed_at,
|
|
cs.resolved_at,
|
|
cs.assigned_at,
|
|
cs.escalated_at,
|
|
cs.last_modified_at,
|
|
cs.first_reviewed_at,
|
|
|
|
-- User IDs
|
|
cs.user_id AS submitted_by,
|
|
cs.reviewer_id AS reviewed_by,
|
|
cs.assigned_to,
|
|
cs.escalated_by,
|
|
cs.last_modified_by,
|
|
|
|
-- State flags
|
|
cs.escalated,
|
|
cs.escalation_reason,
|
|
cs.reviewer_notes AS review_notes,
|
|
cs.approval_mode,
|
|
cs.review_count,
|
|
cs.locked_until,
|
|
cs.is_test_data,
|
|
|
|
-- Submitter profile (aliased for backward compatibility)
|
|
sp.id AS submitter_profile_id,
|
|
sp.username AS submitter_username,
|
|
sp.display_name AS submitter_display_name,
|
|
sp.avatar_url AS submitter_avatar_url,
|
|
sp.reputation_score AS submitter_reputation,
|
|
|
|
-- Reviewer profile
|
|
rp.id AS reviewer_profile_id,
|
|
rp.username AS reviewer_username,
|
|
rp.display_name AS reviewer_display_name,
|
|
rp.avatar_url AS reviewer_avatar_url,
|
|
|
|
-- Assignee profile
|
|
ap.id AS assignee_profile_id,
|
|
ap.username AS assignee_username,
|
|
ap.display_name AS assignee_display_name,
|
|
ap.avatar_url AS assignee_avatar_url,
|
|
|
|
-- Submission items (aggregated with new typed FK columns)
|
|
(
|
|
SELECT json_agg(si.*)
|
|
FROM submission_items si
|
|
WHERE si.submission_id = cs.id
|
|
) as submission_items
|
|
|
|
FROM content_submissions cs
|
|
LEFT JOIN profiles sp ON sp.user_id = cs.user_id
|
|
LEFT JOIN profiles rp ON rp.user_id = cs.reviewer_id
|
|
LEFT JOIN profiles ap ON ap.user_id = cs.assigned_to;
|
|
|
|
COMMENT ON VIEW public.moderation_queue_with_entities IS
|
|
'Optimized view for moderation queue with pre-joined entity data. Uses security_invoker to respect querying user permissions and RLS policies.';
|
|
|
|
-- Fix filtered_profiles view
|
|
DROP VIEW IF EXISTS public.filtered_profiles CASCADE;
|
|
|
|
CREATE VIEW public.filtered_profiles
|
|
WITH (security_invoker = true)
|
|
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_invoker to respect querying user context and RLS policies. Privacy checks use security definer functions for consistent permission enforcement.'; |