diff --git a/alembic/versions/0076_disable_rls_startup_tables.py b/alembic/versions/0076_disable_rls_startup_tables.py new file mode 100644 index 0000000..62b75c9 --- /dev/null +++ b/alembic/versions/0076_disable_rls_startup_tables.py @@ -0,0 +1,65 @@ +"""Disable RLS on startup/system tables that are read without tenant context. + +These tables are accessed during app startup or login before a tenant context +is set. RLS would block these queries and prevent the app from starting. + +Security: These tables are either system-wide (currencies, taxes, sequences, +system_settings) or user-specific (saved_filters, saved_views, webhooks) and +are protected by application-level authorization. + +Revision ID: 0076 +Revises: 0075 +""" + +from alembic import op +from sqlalchemy import text + +revision = "0076" +down_revision = "0075" +branch_labels = None +depends_on = None + +TABLES = [ + "system_settings", + "currencies", + "taxes", + "sequences", + "saved_filters", + "saved_views", + "webhooks", +] + + +def upgrade() -> None: + conn = op.get_bind() + for table in TABLES: + # Check if table exists + exists = conn.execute( + text(f"SELECT 1 FROM information_schema.tables WHERE table_name = '{table}'") + ).fetchone() is not None + if not exists: + continue + + # Drop RLS policy if exists + conn.execute(text(f"DROP POLICY IF EXISTS {table}_tenant_isolation ON {table}")) + # Disable RLS + conn.execute(text(f"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY")) + + +def downgrade() -> None: + conn = op.get_bind() + for table in TABLES: + exists = conn.execute( + text(f"SELECT 1 FROM information_schema.tables WHERE table_name = '{table}'") + ).fetchone() is not None + if not exists: + continue + conn.execute(text(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY")) + conn.execute( + text( + f"CREATE POLICY {table}_tenant_isolation ON {table} " + "FOR ALL " + "USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid) " + "WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::uuid)" + ) + )