181 lines
6.1 KiB
SQL
181 lines
6.1 KiB
SQL
-- ================================================
|
|
-- RLS Policy Tests
|
|
-- Created: 2024-11-15
|
|
-- Epic: E2 - Database & Auth
|
|
-- Story: E2.S2 - RLS policies implementeren
|
|
-- ================================================
|
|
-- This file contains test queries to verify RLS policies
|
|
-- Run these queries manually to verify RLS is working correctly
|
|
-- ================================================
|
|
|
|
-- ================================================
|
|
-- TEST 1: Verify RLS is enabled on all tables
|
|
-- ================================================
|
|
-- Expected: All tables should have rowsecurity = true
|
|
|
|
SELECT
|
|
tablename,
|
|
rowsecurity as rls_enabled
|
|
FROM pg_tables
|
|
WHERE schemaname = 'public'
|
|
ORDER BY tablename;
|
|
|
|
-- Expected output:
|
|
-- ai_events | true
|
|
-- clients | true
|
|
-- intake_notes | true
|
|
-- problem_profiles | true
|
|
-- treatment_plans | true
|
|
|
|
-- ================================================
|
|
-- TEST 2: Check all RLS policies exist
|
|
-- ================================================
|
|
-- Expected: Each table should have 4 policies (SELECT, INSERT, UPDATE, DELETE)
|
|
-- except ai_events which has only 2 (SELECT, INSERT)
|
|
|
|
SELECT
|
|
tablename,
|
|
COUNT(*) as policy_count,
|
|
STRING_AGG(cmd, ', ' ORDER BY cmd) as commands
|
|
FROM pg_policies
|
|
WHERE schemaname = 'public'
|
|
GROUP BY tablename
|
|
ORDER BY tablename;
|
|
|
|
-- Expected output:
|
|
-- ai_events | 2 | INSERT, SELECT
|
|
-- clients | 4 | DELETE, INSERT, SELECT, UPDATE
|
|
-- intake_notes | 4 | DELETE, INSERT, SELECT, UPDATE
|
|
-- problem_profiles | 4 | DELETE, INSERT, SELECT, UPDATE
|
|
-- treatment_plans | 4 | DELETE, INSERT, SELECT, UPDATE
|
|
|
|
-- ================================================
|
|
-- TEST 3: Verify policy predicates use auth.uid()
|
|
-- ================================================
|
|
-- Expected: All policies should check auth.uid() IS NOT NULL
|
|
|
|
SELECT
|
|
tablename,
|
|
policyname,
|
|
cmd,
|
|
qual as using_clause,
|
|
with_check
|
|
FROM pg_policies
|
|
WHERE schemaname = 'public'
|
|
AND qual NOT LIKE '%auth.uid()%'
|
|
ORDER BY tablename, policyname;
|
|
|
|
-- Expected output: Empty (no policies without auth.uid() check)
|
|
|
|
-- ================================================
|
|
-- TEST 4: Simulate authenticated user query
|
|
-- ================================================
|
|
-- This test simulates what happens when an authenticated user
|
|
-- tries to access data. In production, auth.uid() would return
|
|
-- the user's actual UUID.
|
|
|
|
-- Note: These queries will work in the SQL editor when logged in,
|
|
-- but will fail when run as unauthenticated
|
|
|
|
-- Test SELECT permission (should succeed when authenticated)
|
|
-- SELECT * FROM clients LIMIT 1;
|
|
|
|
-- Test INSERT permission (should succeed when authenticated)
|
|
-- INSERT INTO clients (first_name, last_name, birth_date)
|
|
-- VALUES ('Test', 'User', '1990-01-01');
|
|
|
|
-- Test UPDATE permission (should succeed when authenticated)
|
|
-- UPDATE clients SET first_name = 'Updated' WHERE id = 'some-uuid';
|
|
|
|
-- Test DELETE permission (should succeed when authenticated)
|
|
-- DELETE FROM clients WHERE id = 'some-uuid';
|
|
|
|
-- ================================================
|
|
-- TEST 5: Verify ai_events immutability
|
|
-- ================================================
|
|
-- Expected: ai_events should NOT have UPDATE or DELETE policies
|
|
-- (except for service role via RLS bypass)
|
|
|
|
SELECT
|
|
tablename,
|
|
policyname,
|
|
cmd
|
|
FROM pg_policies
|
|
WHERE schemaname = 'public'
|
|
AND tablename = 'ai_events'
|
|
AND cmd IN ('UPDATE', 'DELETE')
|
|
ORDER BY cmd;
|
|
|
|
-- Expected output: Empty (no UPDATE or DELETE policies for regular users)
|
|
|
|
-- ================================================
|
|
-- TEST 6: Check foreign key relationships
|
|
-- ================================================
|
|
-- Expected: All foreign keys should be properly set up
|
|
|
|
SELECT
|
|
tc.table_name,
|
|
kcu.column_name,
|
|
ccu.table_name AS foreign_table_name,
|
|
ccu.column_name AS foreign_column_name,
|
|
rc.delete_rule
|
|
FROM information_schema.table_constraints AS tc
|
|
JOIN information_schema.key_column_usage AS kcu
|
|
ON tc.constraint_name = kcu.constraint_name
|
|
AND tc.table_schema = kcu.table_schema
|
|
JOIN information_schema.constraint_column_usage AS ccu
|
|
ON ccu.constraint_name = tc.constraint_name
|
|
AND ccu.table_schema = tc.table_schema
|
|
JOIN information_schema.referential_constraints AS rc
|
|
ON tc.constraint_name = rc.constraint_name
|
|
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
AND tc.table_schema = 'public'
|
|
ORDER BY tc.table_name, kcu.column_name;
|
|
|
|
-- Expected output:
|
|
-- intake_notes | client_id | clients | id | CASCADE
|
|
-- problem_profiles | client_id | clients | id | CASCADE
|
|
-- problem_profiles | source_note_id | intake_notes | id | SET NULL
|
|
-- treatment_plans | client_id | clients | id | CASCADE
|
|
-- ai_events | client_id | clients | id | SET NULL
|
|
-- ai_events | note_id | intake_notes | id | SET NULL
|
|
|
|
-- ================================================
|
|
-- PRODUCTION MIGRATION PATH
|
|
-- ================================================
|
|
-- When moving to production, enhance policies with org_id filtering:
|
|
--
|
|
-- 1. Add org_id column to all tables:
|
|
-- ALTER TABLE clients ADD COLUMN org_id UUID REFERENCES organizations(id);
|
|
--
|
|
-- 2. Update policies to filter by organization:
|
|
-- CREATE POLICY "Users can view own org clients"
|
|
-- ON clients
|
|
-- FOR SELECT
|
|
-- USING (
|
|
-- auth.uid() IS NOT NULL AND
|
|
-- org_id = (SELECT org_id FROM users WHERE id = auth.uid())
|
|
-- );
|
|
--
|
|
-- 3. Add role-based access:
|
|
-- CREATE POLICY "Admins can view all"
|
|
-- ON clients
|
|
-- FOR SELECT
|
|
-- USING (
|
|
-- auth.uid() IS NOT NULL AND
|
|
-- EXISTS (
|
|
-- SELECT 1 FROM users
|
|
-- WHERE id = auth.uid() AND role = 'admin'
|
|
-- )
|
|
-- );
|
|
|
|
-- ================================================
|
|
-- SECURITY NOTES
|
|
-- ================================================
|
|
-- 1. Current policies are MVP-level: all authenticated users can access all data
|
|
-- 2. In production, add org_id filtering for multi-tenancy
|
|
-- 3. ai_events table is append-only for regular users (audit trail)
|
|
-- 4. Service role can bypass RLS for admin operations
|
|
-- 5. All policies use auth.uid() for security
|
|
-- 6. Foreign key CASCADE ensures orphaned records are cleaned up
|