Both tables have no account_id column — they are globally readable by all authenticated users and must not have RLS policies. Also removes the corresponding test cases that assumed these tables had account_id-based policies. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
86 lines
2.5 KiB
Python
86 lines
2.5 KiB
Python
"""Enable RLS on Phase 4 tables — all remaining tenant-scoped tables.
|
|
|
|
All tables in this migration already have account_id NOT NULL (enforced by
|
|
earlier migrations). This migration adds ENABLE ROW LEVEL SECURITY,
|
|
FORCE ROW LEVEL SECURITY, and the appropriate tenant isolation policy to each.
|
|
|
|
Policy variants used:
|
|
- Standard: account_id = current_setting(app.current_account_id)::uuid
|
|
- Platform: standard OR account_id = PLATFORM_ACCOUNT_ID
|
|
(for global content tables readable by all tenants)
|
|
|
|
Skipped intentionally:
|
|
- accounts — IS the root table; no account_id column
|
|
- plan_feature_defaults — platform config; no account_id column
|
|
- script_categories — global lookup table; no account_id column
|
|
- platform_steps — global content; no account_id column (readable by all)
|
|
- template_trees — global content; no account_id column (readable by all)
|
|
|
|
Revision ID: b3c7e9f2a1d8
|
|
Revises: 172ad76d7d20
|
|
Create Date: 2026-04-12
|
|
"""
|
|
|
|
from typing import Union
|
|
from alembic import op
|
|
|
|
revision: str = "b3c7e9f2a1d8"
|
|
down_revision: Union[str, None] = "172ad76d7d20"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
# Standard policy — tenant sees only own rows.
|
|
_STANDARD_TABLES = [
|
|
"users",
|
|
"account_invites",
|
|
"account_limit_overrides",
|
|
"account_feature_overrides",
|
|
"subscriptions",
|
|
"ai_chat_sessions",
|
|
"ai_conversations",
|
|
"ai_session_steps",
|
|
"ai_session_embeddings",
|
|
"ai_suggestions",
|
|
"ai_usage",
|
|
"assistant_chats",
|
|
"attachments",
|
|
"copilot_conversations",
|
|
"feedback",
|
|
"file_uploads",
|
|
"fork_points",
|
|
"kb_imports",
|
|
"notifications",
|
|
"notification_configs",
|
|
"notification_logs",
|
|
"psa_activity_logs",
|
|
"psa_member_mappings",
|
|
"script_builder_sessions",
|
|
"session_ratings",
|
|
"tree_embeddings",
|
|
"user_folders",
|
|
"user_pinned_trees",
|
|
]
|
|
|
|
_POLICY_EXPR = (
|
|
"account_id = COALESCE("
|
|
"NULLIF(current_setting('app.current_account_id', TRUE), ''), "
|
|
"'00000000-0000-0000-0000-000000000000'"
|
|
")::uuid"
|
|
)
|
|
|
|
|
|
def upgrade() -> None:
|
|
for table in _STANDARD_TABLES:
|
|
op.execute(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY")
|
|
op.execute(f"ALTER TABLE {table} FORCE ROW LEVEL SECURITY")
|
|
op.execute(f"""
|
|
CREATE POLICY tenant_isolation ON {table}
|
|
USING ({_POLICY_EXPR})
|
|
""")
|
|
|
|
|
|
def downgrade() -> None:
|
|
for table in _STANDARD_TABLES:
|
|
op.execute(f"DROP POLICY IF EXISTS tenant_isolation ON {table}")
|
|
op.execute(f"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY")
|