bookwiz.io / supabase / migrations / 021_fix_rls_performance.sql
021_fix_rls_performance.sql
Raw
-- Fix RLS performance issues reported by database linter
-- This migration addresses:
-- 1. Auth RLS Initialization Plan issues - replace auth.uid() with (select auth.uid())
-- 2. Multiple Permissive Policies issues - consolidate policies where possible

-- ============================================================================
-- 1. FIX PROFILES TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can insert their own profile." ON public.profiles;
DROP POLICY IF EXISTS "Users can update their own profile." ON public.profiles;

-- Recreate with optimized auth calls
CREATE POLICY "Users can insert their own profile." ON public.profiles
  FOR INSERT WITH CHECK ((select auth.uid()) = id);

CREATE POLICY "Users can update their own profile." ON public.profiles
  FOR UPDATE USING ((select auth.uid()) = id);

-- ============================================================================
-- 2. FIX BOOKS TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view their own books" ON books;
DROP POLICY IF EXISTS "Users can create their own books" ON books;
DROP POLICY IF EXISTS "Users can update their own books" ON books;
DROP POLICY IF EXISTS "Users can delete their own books" ON books;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view their own books" ON books
    FOR SELECT USING ((select auth.uid()) = user_id);

CREATE POLICY "Users can create their own books" ON books
    FOR INSERT WITH CHECK ((select auth.uid()) = user_id);

CREATE POLICY "Users can update their own books" ON books
    FOR UPDATE USING ((select auth.uid()) = user_id);

CREATE POLICY "Users can delete their own books" ON books
    FOR DELETE USING ((select auth.uid()) = user_id);

-- ============================================================================
-- 3. FIX FILE_SYSTEM_ITEMS TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view their own file system items" ON file_system_items;
DROP POLICY IF EXISTS "Users can create file system items in their books" ON file_system_items;
DROP POLICY IF EXISTS "Users can update file system items in their books" ON file_system_items;
DROP POLICY IF EXISTS "Users can delete file system items in their books" ON file_system_items;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view their own file system items" ON file_system_items
    FOR SELECT USING (
        EXISTS (
            SELECT 1 FROM books 
            WHERE books.id = file_system_items.book_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can create file system items in their books" ON file_system_items
    FOR INSERT WITH CHECK (
        EXISTS (
            SELECT 1 FROM books 
            WHERE books.id = file_system_items.book_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can update file system items in their books" ON file_system_items
    FOR UPDATE USING (
        EXISTS (
            SELECT 1 FROM books 
            WHERE books.id = file_system_items.book_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can delete file system items in their books" ON file_system_items
    FOR DELETE USING (
        EXISTS (
            SELECT 1 FROM books 
            WHERE books.id = file_system_items.book_id 
            AND books.user_id = (select auth.uid())
        )
    );

-- ============================================================================
-- 4. FIX CHATS TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view their own chats" ON chats;
DROP POLICY IF EXISTS "Users can create chats in their books" ON chats;
DROP POLICY IF EXISTS "Users can update their own chats" ON chats;
DROP POLICY IF EXISTS "Users can delete their own chats" ON chats;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view their own chats" ON chats
    FOR SELECT USING (
        EXISTS (
            SELECT 1 FROM books 
            WHERE books.id = chats.book_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can create chats in their books" ON chats
    FOR INSERT WITH CHECK (
        EXISTS (
            SELECT 1 FROM books 
            WHERE books.id = chats.book_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can update their own chats" ON chats
    FOR UPDATE USING (
        EXISTS (
            SELECT 1 FROM books 
            WHERE books.id = chats.book_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can delete their own chats" ON chats
    FOR DELETE USING (
        EXISTS (
            SELECT 1 FROM books 
            WHERE books.id = chats.book_id 
            AND books.user_id = (select auth.uid())
        )
    );

-- ============================================================================
-- 5. FIX MESSAGES TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view messages in their chats" ON messages;
DROP POLICY IF EXISTS "Users can create messages in their chats" ON messages;
DROP POLICY IF EXISTS "Users can update messages in their chats" ON messages;
DROP POLICY IF EXISTS "Users can delete messages in their chats" ON messages;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view messages in their chats" ON messages
    FOR SELECT USING (
        EXISTS (
            SELECT 1 FROM chats 
            JOIN books ON books.id = chats.book_id
            WHERE chats.id = messages.chat_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can create messages in their chats" ON messages
    FOR INSERT WITH CHECK (
        EXISTS (
            SELECT 1 FROM chats 
            JOIN books ON books.id = chats.book_id
            WHERE chats.id = messages.chat_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can update messages in their chats" ON messages
    FOR UPDATE USING (
        EXISTS (
            SELECT 1 FROM chats 
            JOIN books ON books.id = chats.book_id
            WHERE chats.id = messages.chat_id 
            AND books.user_id = (select auth.uid())
        )
    );

CREATE POLICY "Users can delete messages in their chats" ON messages
    FOR DELETE USING (
        EXISTS (
            SELECT 1 FROM chats 
            JOIN books ON books.id = chats.book_id
            WHERE chats.id = messages.chat_id 
            AND books.user_id = (select auth.uid())
        )
    );

-- ============================================================================
-- 6. FIX AI_USAGE TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view own AI usage" ON ai_usage;
DROP POLICY IF EXISTS "System can insert AI usage" ON ai_usage;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view own AI usage" ON ai_usage
    FOR SELECT USING ((select auth.uid()) = user_id);

CREATE POLICY "System can insert AI usage" ON ai_usage
    FOR INSERT WITH CHECK ((select auth.uid()) = user_id);

-- ============================================================================
-- 7. FIX AI_OPERATIONS TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view their own AI operations" ON ai_operations;
DROP POLICY IF EXISTS "Users can create AI operations for their books" ON ai_operations;
DROP POLICY IF EXISTS "Users can update their own AI operations" ON ai_operations;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view their own AI operations" ON ai_operations
    FOR SELECT USING (
        -- Allow if user_id matches auth.uid()
        (select auth.uid()) = user_id OR
        -- Allow if user_id is null but book belongs to authenticated user
        (user_id IS NULL AND
         EXISTS (
             SELECT 1 FROM books 
             WHERE books.id = ai_operations.book_id 
             AND books.user_id = (select auth.uid())
         ))
    );

CREATE POLICY "Users can create AI operations for their books" ON ai_operations
    FOR INSERT WITH CHECK (
        -- Allow if user_id matches auth.uid() and book belongs to user
        ((select auth.uid()) = user_id AND
         EXISTS (
             SELECT 1 FROM books 
             WHERE books.id = ai_operations.book_id 
             AND books.user_id = (select auth.uid())
         )) OR
        -- Allow if user_id is null but book belongs to authenticated user
        (user_id IS NULL AND
         EXISTS (
             SELECT 1 FROM books 
             WHERE books.id = ai_operations.book_id 
             AND books.user_id = (select auth.uid())
         ))
    );

CREATE POLICY "Users can update their own AI operations" ON ai_operations
    FOR UPDATE USING (
        -- Allow if user_id matches auth.uid()
        (select auth.uid()) = user_id OR
        -- Allow if user_id is null but book belongs to authenticated user
        (user_id IS NULL AND
         EXISTS (
             SELECT 1 FROM books 
             WHERE books.id = ai_operations.book_id 
             AND books.user_id = (select auth.uid())
         ))
    );

-- ============================================================================
-- 8. FIX FILE_SNAPSHOTS TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view snapshots for their operations" ON file_snapshots;
DROP POLICY IF EXISTS "Users can create snapshots for their operations" ON file_snapshots;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view snapshots for their operations" ON file_snapshots
    FOR SELECT USING (
        EXISTS (
            SELECT 1 FROM ai_operations 
            WHERE ai_operations.id = file_snapshots.ai_operation_id 
            AND (
                ai_operations.user_id = (select auth.uid()) OR
                (ai_operations.user_id IS NULL AND
                 EXISTS (
                     SELECT 1 FROM books 
                     WHERE books.id = ai_operations.book_id 
                     AND books.user_id = (select auth.uid())
                 ))
            )
        )
    );

CREATE POLICY "Users can create snapshots for their operations" ON file_snapshots
    FOR INSERT WITH CHECK (
        EXISTS (
            SELECT 1 FROM ai_operations 
            WHERE ai_operations.id = file_snapshots.ai_operation_id 
            AND (
                ai_operations.user_id = (select auth.uid()) OR
                (ai_operations.user_id IS NULL AND
                 EXISTS (
                     SELECT 1 FROM books 
                     WHERE books.id = ai_operations.book_id 
                     AND books.user_id = (select auth.uid())
                 ))
            )
        )
    );

-- ============================================================================
-- 9. FIX USER_USAGE TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view their own usage" ON user_usage;
DROP POLICY IF EXISTS "Users can insert their own usage" ON user_usage;
DROP POLICY IF EXISTS "Users can update their own usage" ON user_usage;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view their own usage" ON user_usage
  FOR SELECT USING ((select auth.uid()) = user_id);

CREATE POLICY "Users can insert their own usage" ON user_usage
  FOR INSERT WITH CHECK ((select auth.uid()) = user_id);

CREATE POLICY "Users can update their own usage" ON user_usage
  FOR UPDATE USING ((select auth.uid()) = user_id);

-- ============================================================================
-- 10. FIX USAGE_LOGS TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view their own usage logs" ON usage_logs;
DROP POLICY IF EXISTS "Users can insert their own usage logs" ON usage_logs;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view their own usage logs" ON usage_logs
  FOR SELECT USING ((select auth.uid()) = user_id);

CREATE POLICY "Users can insert their own usage logs" ON usage_logs
  FOR INSERT WITH CHECK ((select auth.uid()) = user_id);

-- ============================================================================
-- 11. FIX SUBSCRIPTIONS TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view their own subscription" ON subscriptions;
DROP POLICY IF EXISTS "Users can insert their own subscription" ON subscriptions;
DROP POLICY IF EXISTS "Users can update their own subscription" ON subscriptions;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view their own subscription" ON subscriptions
  FOR SELECT USING ((select auth.uid()) = user_id);

CREATE POLICY "Users can insert their own subscription" ON subscriptions
  FOR INSERT WITH CHECK ((select auth.uid()) = user_id);

CREATE POLICY "Users can update their own subscription" ON subscriptions
  FOR UPDATE USING ((select auth.uid()) = user_id);

-- ============================================================================
-- 12. FIX EMAIL_PREFERENCES TABLE POLICIES
-- ============================================================================

-- Drop existing policies
DROP POLICY IF EXISTS "Users can view their own email preferences" ON email_preferences;
DROP POLICY IF EXISTS "Users can insert their own email preferences" ON email_preferences;
DROP POLICY IF EXISTS "Users can update their own email preferences" ON email_preferences;

-- Recreate with optimized auth calls
CREATE POLICY "Users can view their own email preferences" ON email_preferences
  FOR SELECT USING ((select auth.uid()) = user_id);

CREATE POLICY "Users can insert their own email preferences" ON email_preferences
  FOR INSERT WITH CHECK ((select auth.uid()) = user_id);

CREATE POLICY "Users can update their own email preferences" ON email_preferences
  FOR UPDATE USING ((select auth.uid()) = user_id);

-- ============================================================================
-- 13. FIX BOOK_TEMPLATES TABLE POLICIES (CONSOLIDATE MULTIPLE PERMISSIVE)
-- ============================================================================

-- Drop existing policies that cause multiple permissive issues
DROP POLICY IF EXISTS "Everyone can view system templates" ON book_templates;
DROP POLICY IF EXISTS "Users can view their own custom templates" ON book_templates;
DROP POLICY IF EXISTS "Users can create custom templates" ON book_templates;
DROP POLICY IF EXISTS "Users can update their own custom templates" ON book_templates;
DROP POLICY IF EXISTS "Users can delete their own custom templates" ON book_templates;

-- Create consolidated policies to avoid multiple permissive issues
-- Single SELECT policy that handles both system templates and user templates
CREATE POLICY "Users can view templates" ON book_templates
    FOR SELECT USING (
        is_system = true OR created_by = (select auth.uid())
    );

-- Keep separate policies for INSERT, UPDATE, DELETE as they don't conflict
CREATE POLICY "Users can create custom templates" ON book_templates
    FOR INSERT WITH CHECK (created_by = (select auth.uid()) AND is_system = false);

CREATE POLICY "Users can update their own custom templates" ON book_templates
    FOR UPDATE USING (created_by = (select auth.uid()) AND is_system = false);

CREATE POLICY "Users can delete their own custom templates" ON book_templates
    FOR DELETE USING (created_by = (select auth.uid()) AND is_system = false);

-- ============================================================================
-- MIGRATION COMPLETE
-- ============================================================================

-- Add helpful comment
COMMENT ON SCHEMA public IS 'RLS policies optimized for performance - auth.uid() calls wrapped in SELECT subqueries to prevent re-evaluation per row';