-- Row-Level Security (RLS) Policies -- Run this after migrations to enable tenant isolation at the DB level. -- Helper: current tenant context (set via SET LOCAL in Prisma extension) -- Usage: SET LOCAL app.current_tenant_id = ''; -- ─── Enable RLS ────────────────────────────────────────── ALTER TABLE users ENABLE ROW LEVEL SECURITY; ALTER TABLE boards ENABLE ROW LEVEL SECURITY; ALTER TABLE board_members ENABLE ROW LEVEL SECURITY; ALTER TABLE columns ENABLE ROW LEVEL SECURITY; ALTER TABLE cards ENABLE ROW LEVEL SECURITY; ALTER TABLE card_assignees ENABLE ROW LEVEL SECURITY; ALTER TABLE tags ENABLE ROW LEVEL SECURITY; ALTER TABLE card_labels ENABLE ROW LEVEL SECURITY; ALTER TABLE categories ENABLE ROW LEVEL SECURITY; ALTER TABLE checklists ENABLE ROW LEVEL SECURITY; ALTER TABLE checklist_items ENABLE ROW LEVEL SECURITY; ALTER TABLE comments ENABLE ROW LEVEL SECURITY; ALTER TABLE attachments ENABLE ROW LEVEL SECURITY; ALTER TABLE activities ENABLE ROW LEVEL SECURITY; ALTER TABLE notifications ENABLE ROW LEVEL SECURITY; ALTER TABLE board_templates ENABLE ROW LEVEL SECURITY; -- ─── Tenant-scoped tables (direct tenant_id) ──────────── -- Users DROP POLICY IF EXISTS users_tenant_isolation ON users; CREATE POLICY users_tenant_isolation ON users USING (tenant_id::text = current_setting('app.current_tenant_id', true)); -- Boards DROP POLICY IF EXISTS boards_tenant_isolation ON boards; CREATE POLICY boards_tenant_isolation ON boards USING (tenant_id::text = current_setting('app.current_tenant_id', true)); -- Tags DROP POLICY IF EXISTS tags_tenant_isolation ON tags; CREATE POLICY tags_tenant_isolation ON tags USING (tenant_id::text = current_setting('app.current_tenant_id', true)); -- Categories DROP POLICY IF EXISTS categories_tenant_isolation ON categories; CREATE POLICY categories_tenant_isolation ON categories USING (tenant_id::text = current_setting('app.current_tenant_id', true)); -- Board Templates (null tenant_id = system template, visible to all) DROP POLICY IF EXISTS board_templates_tenant_isolation ON board_templates; CREATE POLICY board_templates_tenant_isolation ON board_templates USING ( tenant_id IS NULL OR tenant_id::text = current_setting('app.current_tenant_id', true) ); -- ─── Board-scoped tables (via join to boards) ─────────── -- Board Members DROP POLICY IF EXISTS board_members_tenant_isolation ON board_members; CREATE POLICY board_members_tenant_isolation ON board_members USING ( board_id IN ( SELECT id FROM boards WHERE tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Columns DROP POLICY IF EXISTS columns_tenant_isolation ON columns; CREATE POLICY columns_tenant_isolation ON columns USING ( board_id IN ( SELECT id FROM boards WHERE tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Activities DROP POLICY IF EXISTS activities_tenant_isolation ON activities; CREATE POLICY activities_tenant_isolation ON activities USING ( board_id IN ( SELECT id FROM boards WHERE tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- ─── Card-scoped tables (via join to columns -> boards) ─ -- Cards DROP POLICY IF EXISTS cards_tenant_isolation ON cards; CREATE POLICY cards_tenant_isolation ON cards USING ( column_id IN ( SELECT c.id FROM columns c JOIN boards b ON c.board_id = b.id WHERE b.tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Card Assignees DROP POLICY IF EXISTS card_assignees_tenant_isolation ON card_assignees; CREATE POLICY card_assignees_tenant_isolation ON card_assignees USING ( card_id IN ( SELECT ca.id FROM cards ca JOIN columns c ON ca.column_id = c.id JOIN boards b ON c.board_id = b.id WHERE b.tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Card Labels DROP POLICY IF EXISTS card_labels_tenant_isolation ON card_labels; CREATE POLICY card_labels_tenant_isolation ON card_labels USING ( card_id IN ( SELECT ca.id FROM cards ca JOIN columns c ON ca.column_id = c.id JOIN boards b ON c.board_id = b.id WHERE b.tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Checklists DROP POLICY IF EXISTS checklists_tenant_isolation ON checklists; CREATE POLICY checklists_tenant_isolation ON checklists USING ( card_id IN ( SELECT ca.id FROM cards ca JOIN columns c ON ca.column_id = c.id JOIN boards b ON c.board_id = b.id WHERE b.tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Checklist Items DROP POLICY IF EXISTS checklist_items_tenant_isolation ON checklist_items; CREATE POLICY checklist_items_tenant_isolation ON checklist_items USING ( checklist_id IN ( SELECT ch.id FROM checklists ch JOIN cards ca ON ch.card_id = ca.id JOIN columns c ON ca.column_id = c.id JOIN boards b ON c.board_id = b.id WHERE b.tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Comments DROP POLICY IF EXISTS comments_tenant_isolation ON comments; CREATE POLICY comments_tenant_isolation ON comments USING ( card_id IN ( SELECT ca.id FROM cards ca JOIN columns c ON ca.column_id = c.id JOIN boards b ON c.board_id = b.id WHERE b.tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Attachments DROP POLICY IF EXISTS attachments_tenant_isolation ON attachments; CREATE POLICY attachments_tenant_isolation ON attachments USING ( card_id IN ( SELECT ca.id FROM cards ca JOIN columns c ON ca.column_id = c.id JOIN boards b ON c.board_id = b.id WHERE b.tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- ─── User-scoped tables ───────────────────────────────── -- Notifications DROP POLICY IF EXISTS notifications_tenant_isolation ON notifications; CREATE POLICY notifications_tenant_isolation ON notifications USING ( user_id IN ( SELECT id FROM users WHERE tenant_id::text = current_setting('app.current_tenant_id', true) ) ); -- Sessions (no RLS - cross-tenant by design for session validation) -- Sessions are validated in application code with tenant check