Files
root-org/supabase/migrations/051_rls_performance_fixes.sql
2026-02-09 11:36:39 +02:00

585 lines
19 KiB
SQL

-- ============================================================
-- Migration 051: RLS Performance Fixes
-- Addresses all Supabase linter warnings:
-- 1. auth_rls_initplan: wrap auth.uid() in (select auth.uid())
-- 2. multiple_permissive_policies: consolidate overlapping policies
-- ============================================================
-- ============================================================
-- PART 1: Fix auth_rls_initplan
-- Replace auth.uid() with (select auth.uid()) in ALL RLS policies
-- using a dynamic DO block that reads pg_policies catalog
-- ============================================================
DO $$
DECLARE
r record;
qual_new text;
check_new text;
cmd_parts text[];
sql_cmd text;
BEGIN
FOR r IN
SELECT
schemaname,
tablename,
policyname,
permissive,
roles,
cmd AS policy_cmd,
qual,
with_check
FROM pg_policies
WHERE schemaname = 'public'
AND (
qual ~ 'auth\.uid\(\)' AND qual !~ '\(\s*select\s+auth\.uid\(\)\s*\)'
OR
with_check ~ 'auth\.uid\(\)' AND with_check !~ '\(\s*select\s+auth\.uid\(\)\s*\)'
)
LOOP
-- Replace auth.uid() with (select auth.uid()) in USING clause
qual_new := r.qual;
IF qual_new IS NOT NULL THEN
-- Avoid double-wrapping: only replace bare auth.uid() not already in (select ...)
qual_new := regexp_replace(qual_new, '(?<!\(select )auth\.uid\(\)', '(select auth.uid())', 'gi');
END IF;
-- Replace auth.uid() with (select auth.uid()) in WITH CHECK clause
check_new := r.with_check;
IF check_new IS NOT NULL THEN
check_new := regexp_replace(check_new, '(?<!\(select )auth\.uid\(\)', '(select auth.uid())', 'gi');
END IF;
-- Drop old policy
EXECUTE format('DROP POLICY IF EXISTS %I ON %I.%I',
r.policyname, r.schemaname, r.tablename);
-- Build CREATE POLICY command
sql_cmd := format('CREATE POLICY %I ON %I.%I', r.policyname, r.schemaname, r.tablename);
-- AS PERMISSIVE/RESTRICTIVE
IF r.permissive = 'false' THEN
sql_cmd := sql_cmd || ' AS RESTRICTIVE';
END IF;
-- FOR command
sql_cmd := sql_cmd || ' FOR ' || r.policy_cmd;
-- TO roles
sql_cmd := sql_cmd || ' TO ' || array_to_string(r.roles, ', ');
-- USING clause
IF qual_new IS NOT NULL THEN
sql_cmd := sql_cmd || ' USING (' || qual_new || ')';
END IF;
-- WITH CHECK clause
IF check_new IS NOT NULL THEN
sql_cmd := sql_cmd || ' WITH CHECK (' || check_new || ')';
END IF;
EXECUTE sql_cmd;
RAISE NOTICE 'Fixed initplan: %.% policy "%"', r.schemaname, r.tablename, r.policyname;
END LOOP;
END;
$$;
-- ============================================================
-- PART 2: Fix multiple_permissive_policies
-- Pattern A: Tables with "Org members can view X" (SELECT) +
-- "Editors can manage X" (FOR ALL) → drop the SELECT policy
-- since FOR ALL already covers SELECT.
-- Pattern B: Tables with "Platform admins full access" (FOR ALL) +
-- "Editors can manage X" (FOR ALL) → merge platform admin
-- check into the editors policy, drop the platform admin policy.
-- Pattern C: Tables with "Org members can view X" (SELECT) +
-- "Members can manage X" (FOR ALL) → same as Pattern A.
-- ============================================================
-- --------------------------------------------------------
-- Pattern A: Drop redundant SELECT-only policies
-- These tables have both "view" (SELECT) and "manage" (FOR ALL)
-- The FOR ALL already covers SELECT, making the view policy redundant
-- --------------------------------------------------------
-- sponsors
DROP POLICY IF EXISTS "Org members can view sponsors" ON public.sponsors;
-- sponsor_deliverables
DROP POLICY IF EXISTS "Org members can view sponsor deliverables" ON public.sponsor_deliverables;
-- sponsor_tiers
DROP POLICY IF EXISTS "Org members can view sponsor tiers" ON public.sponsor_tiers;
-- documents
DROP POLICY IF EXISTS "Org members can view documents" ON public.documents;
-- kanban_boards
DROP POLICY IF EXISTS "Org members can view boards" ON public.kanban_boards;
-- kanban_columns
DROP POLICY IF EXISTS "Org members can view columns" ON public.kanban_columns;
-- kanban_cards
DROP POLICY IF EXISTS "Org members can view cards" ON public.kanban_cards;
-- calendar_events
DROP POLICY IF EXISTS "Org members can view events" ON public.calendar_events;
-- events
DROP POLICY IF EXISTS "Org members can view events" ON public.events;
-- event_members
DROP POLICY IF EXISTS "Org members can view event members" ON public.event_members;
-- event_roles
DROP POLICY IF EXISTS "Org members can view event roles" ON public.event_roles;
-- event_departments
DROP POLICY IF EXISTS "Org members can view event departments" ON public.event_departments;
-- event_member_departments
DROP POLICY IF EXISTS "Org members can view member departments" ON public.event_member_departments;
-- event_task_columns
DROP POLICY IF EXISTS "Org members can view event task columns" ON public.event_task_columns;
-- event_tasks
DROP POLICY IF EXISTS "Org members can view event tasks" ON public.event_tasks;
-- department_dashboards
DROP POLICY IF EXISTS "Org members can view department dashboards" ON public.department_dashboards;
-- dashboard_panels
DROP POLICY IF EXISTS "Org members can view dashboard panels" ON public.dashboard_panels;
-- department_checklists
DROP POLICY IF EXISTS "Org members can view department checklists" ON public.department_checklists;
-- department_checklist_items
DROP POLICY IF EXISTS "Org members can view dept checklist items" ON public.department_checklist_items;
-- department_notes
DROP POLICY IF EXISTS "Org members can view department notes" ON public.department_notes;
-- checklist_items
DROP POLICY IF EXISTS "Org members can view checklist items" ON public.checklist_items;
-- budget_categories
DROP POLICY IF EXISTS "Org members can view budget categories" ON public.budget_categories;
-- budget_items
DROP POLICY IF EXISTS "Org members can view budget items" ON public.budget_items;
-- card_tags: "Users can view card tags" + "Members can manage card tags" (FOR ALL)
DROP POLICY IF EXISTS "Users can view card tags in their orgs" ON public.card_tags;
-- document_locks: "Anyone can delete expired locks" + "Users can delete their own locks"
-- Merge into single DELETE policy
DROP POLICY IF EXISTS "Anyone can delete expired locks" ON public.document_locks;
DROP POLICY IF EXISTS "Users can delete their own locks" ON public.document_locks;
CREATE POLICY "Users can delete locks" ON public.document_locks
FOR DELETE
USING (
user_id = (select auth.uid())
OR last_heartbeat < (now() - interval '2 minutes')
);
-- --------------------------------------------------------
-- Pattern B: Merge "Platform admins full access" into existing policies
-- For tables that have both "Editors can manage X" and
-- "Platform admins full access to X", we merge the admin check
-- into the editors policy using OR is_platform_admin(), then
-- drop the separate platform admin policy.
-- --------------------------------------------------------
-- Note: The is_platform_admin() function was already fixed in migration 039
-- to use (select auth.uid()). We just need to merge the policies.
-- For tables with platform admin policies, we need to update the
-- "Editors can manage" policy to include OR (select public.is_platform_admin())
-- then drop the platform admin policy.
-- calendar_events
DROP POLICY IF EXISTS "Platform admins full access to calendar_events" ON public.calendar_events;
DROP POLICY IF EXISTS "Editors can manage events" ON public.calendar_events;
CREATE POLICY "Editors can manage events" ON public.calendar_events
FOR ALL
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = calendar_events.org_id
AND om.user_id = (select auth.uid())
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = calendar_events.org_id
AND om.user_id = (select auth.uid())
)
);
-- documents
DROP POLICY IF EXISTS "Platform admins full access to documents" ON public.documents;
DROP POLICY IF EXISTS "Editors can manage documents" ON public.documents;
CREATE POLICY "Editors can manage documents" ON public.documents
FOR ALL
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = documents.org_id
AND om.user_id = (select auth.uid())
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = documents.org_id
AND om.user_id = (select auth.uid())
)
);
-- events
DROP POLICY IF EXISTS "Platform admins full access to events" ON public.events;
DROP POLICY IF EXISTS "Editors can manage events" ON public.events;
CREATE POLICY "Editors can manage events" ON public.events
FOR ALL
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = events.org_id
AND om.user_id = (select auth.uid())
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = events.org_id
AND om.user_id = (select auth.uid())
)
);
-- event_members
DROP POLICY IF EXISTS "Platform admins full access to event_members" ON public.event_members;
DROP POLICY IF EXISTS "Editors can manage event members" ON public.event_members;
CREATE POLICY "Editors can manage event members" ON public.event_members
FOR ALL
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.events e
JOIN public.org_members om ON om.org_id = e.org_id
WHERE e.id = event_members.event_id
AND om.user_id = (select auth.uid())
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.events e
JOIN public.org_members om ON om.org_id = e.org_id
WHERE e.id = event_members.event_id
AND om.user_id = (select auth.uid())
)
);
-- event_departments
DROP POLICY IF EXISTS "Platform admins full access to event_departments" ON public.event_departments;
DROP POLICY IF EXISTS "Editors can manage event departments" ON public.event_departments;
CREATE POLICY "Editors can manage event departments" ON public.event_departments
FOR ALL
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.events e
JOIN public.org_members om ON om.org_id = e.org_id
WHERE e.id = event_departments.event_id
AND om.user_id = (select auth.uid())
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.events e
JOIN public.org_members om ON om.org_id = e.org_id
WHERE e.id = event_departments.event_id
AND om.user_id = (select auth.uid())
)
);
-- kanban_boards
DROP POLICY IF EXISTS "Platform admins full access to kanban_boards" ON public.kanban_boards;
DROP POLICY IF EXISTS "Editors can manage boards" ON public.kanban_boards;
CREATE POLICY "Editors can manage boards" ON public.kanban_boards
FOR ALL
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = kanban_boards.org_id
AND om.user_id = (select auth.uid())
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = kanban_boards.org_id
AND om.user_id = (select auth.uid())
)
);
-- org_roles
DROP POLICY IF EXISTS "Platform admins full access to org_roles" ON public.org_roles;
-- org_roles has "Org members can view roles" + "Admins can manage roles"
DROP POLICY IF EXISTS "Org members can view roles" ON public.org_roles;
DROP POLICY IF EXISTS "Admins can manage roles" ON public.org_roles;
CREATE POLICY "Admins can manage roles" ON public.org_roles
FOR ALL
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = org_roles.org_id
AND om.user_id = (select auth.uid())
AND om.role IN ('owner', 'admin')
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = org_roles.org_id
AND om.user_id = (select auth.uid())
AND om.role IN ('owner', 'admin')
)
);
-- Org members still need to SELECT roles (for UI dropdowns etc)
CREATE POLICY "Org members can view roles" ON public.org_roles
FOR SELECT
USING (
EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = org_roles.org_id
AND om.user_id = (select auth.uid())
)
);
-- org_invites
DROP POLICY IF EXISTS "Platform admins full access to org_invites" ON public.org_invites;
-- org_invites has "Org members can view invites" + "Admins can manage invites"
DROP POLICY IF EXISTS "Org members can view invites" ON public.org_invites;
DROP POLICY IF EXISTS "Admins can manage invites" ON public.org_invites;
CREATE POLICY "Admins can manage invites" ON public.org_invites
FOR ALL
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = org_invites.org_id
AND om.user_id = (select auth.uid())
AND om.role IN ('owner', 'admin')
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = org_invites.org_id
AND om.user_id = (select auth.uid())
AND om.role IN ('owner', 'admin')
)
);
-- Org members still need to SELECT invites
CREATE POLICY "Org members can view invites" ON public.org_invites
FOR SELECT
USING (
EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = org_invites.org_id
AND om.user_id = (select auth.uid())
)
);
-- organizations
DROP POLICY IF EXISTS "Platform admins full access to organizations" ON public.organizations;
-- organizations already has separate view/update/delete policies, platform admin just adds another FOR ALL
-- Merge into existing policies by adding OR is_platform_admin()
DROP POLICY IF EXISTS "Members can view their organizations" ON public.organizations;
CREATE POLICY "Members can view their organizations" ON public.organizations
FOR SELECT
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = organizations.id
AND om.user_id = (select auth.uid())
)
);
DROP POLICY IF EXISTS "Owners and admins can update organizations" ON public.organizations;
CREATE POLICY "Owners and admins can update organizations" ON public.organizations
FOR UPDATE
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = organizations.id
AND om.user_id = (select auth.uid())
AND om.role IN ('owner', 'admin')
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = organizations.id
AND om.user_id = (select auth.uid())
AND om.role IN ('owner', 'admin')
)
);
DROP POLICY IF EXISTS "Owners can delete organizations" ON public.organizations;
CREATE POLICY "Owners can delete organizations" ON public.organizations
FOR DELETE
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om
WHERE om.org_id = organizations.id
AND om.user_id = (select auth.uid())
AND om.role = 'owner'
)
);
-- org_members
DROP POLICY IF EXISTS "Platform admins full access to org_members" ON public.org_members;
-- org_members has separate view/manage/delete policies + Allow member inserts
-- Just add platform admin to the view policy
DROP POLICY IF EXISTS "Members can view org members" ON public.org_members;
CREATE POLICY "Members can view org members" ON public.org_members
FOR SELECT
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om2
WHERE om2.org_id = org_members.org_id
AND om2.user_id = (select auth.uid())
)
);
DROP POLICY IF EXISTS "Owners and admins can manage members" ON public.org_members;
CREATE POLICY "Owners and admins can manage members" ON public.org_members
FOR UPDATE
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om2
WHERE om2.org_id = org_members.org_id
AND om2.user_id = (select auth.uid())
AND om2.role IN ('owner', 'admin')
)
)
WITH CHECK (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om2
WHERE om2.org_id = org_members.org_id
AND om2.user_id = (select auth.uid())
AND om2.role IN ('owner', 'admin')
)
);
DROP POLICY IF EXISTS "Owners and admins can delete members" ON public.org_members;
CREATE POLICY "Owners and admins can delete members" ON public.org_members
FOR DELETE
USING (
(select public.is_platform_admin())
OR EXISTS (
SELECT 1 FROM public.org_members om2
WHERE om2.org_id = org_members.org_id
AND om2.user_id = (select auth.uid())
AND om2.role IN ('owner', 'admin')
)
);
-- ============================================================
-- PART 3: Run the initplan fixer AGAIN after Part 2
-- Part 2 created new policies that may still use bare auth.uid()
-- in the USING/WITH CHECK from the original policy definitions.
-- This second pass catches any remaining bare auth.uid() calls.
-- ============================================================
DO $$
DECLARE
r record;
qual_new text;
check_new text;
sql_cmd text;
BEGIN
FOR r IN
SELECT
schemaname,
tablename,
policyname,
permissive,
roles,
cmd AS policy_cmd,
qual,
with_check
FROM pg_policies
WHERE schemaname = 'public'
AND (
qual ~ 'auth\.uid\(\)' AND qual !~ '\(\s*select\s+auth\.uid\(\)\s*\)'
OR
with_check ~ 'auth\.uid\(\)' AND with_check !~ '\(\s*select\s+auth\.uid\(\)\s*\)'
)
LOOP
qual_new := r.qual;
IF qual_new IS NOT NULL THEN
qual_new := regexp_replace(qual_new, '(?<!\(select )auth\.uid\(\)', '(select auth.uid())', 'gi');
END IF;
check_new := r.with_check;
IF check_new IS NOT NULL THEN
check_new := regexp_replace(check_new, '(?<!\(select )auth\.uid\(\)', '(select auth.uid())', 'gi');
END IF;
EXECUTE format('DROP POLICY IF EXISTS %I ON %I.%I',
r.policyname, r.schemaname, r.tablename);
sql_cmd := format('CREATE POLICY %I ON %I.%I', r.policyname, r.schemaname, r.tablename);
IF r.permissive = 'false' THEN
sql_cmd := sql_cmd || ' AS RESTRICTIVE';
END IF;
sql_cmd := sql_cmd || ' FOR ' || r.policy_cmd;
sql_cmd := sql_cmd || ' TO ' || array_to_string(r.roles, ', ');
IF qual_new IS NOT NULL THEN
sql_cmd := sql_cmd || ' USING (' || qual_new || ')';
END IF;
IF check_new IS NOT NULL THEN
sql_cmd := sql_cmd || ' WITH CHECK (' || check_new || ')';
END IF;
EXECUTE sql_cmd;
RAISE NOTICE 'Fixed initplan (pass 2): %.% policy "%"', r.schemaname, r.tablename, r.policyname;
END LOOP;
END;
$$;