Files
leocrm/alembic/versions/0109_disable_rls_on_login_related_tables.py
T

67 lines
2.2 KiB
Python

"""Disable RLS on login-related tables (roles, permissions).
⚠️ KI / AGENT HINWEIS — BITTE VOR ÄNDERUNGEN LESEN ⚠️
Migration 0108 hat RLS auf alle Tabellen mit tenant_id aktiviert, darunter auch
`roles` und `permissions`. Diese Tabellen werden vom Login-Flow gelesen um die
Benutzerrolle zu bestimmen. Mit RLS auf diesen Tabellen und keinem tenant_id
Kontext beim Login → Query gibt nichts zurück → Login schlägt fehl mit 401.
Diese Tabellen MÜSSEN ohne RLS bleiben, genau wie users, sessions, tenants,
user_tenants, password_reset_tokens (siehe 0108 LOGIN_TABLES).
Lektion: RLS darf NICHT auf Tabellen aktiviert werden die vom Login-Flow
gelesen werden, wenn der Login-Flow noch keinen tenant_id Kontext hat.
Revision ID: 0109
Revises: 0108
"""
from __future__ import annotations
from alembic import op
revision = "0109"
down_revision = "0108"
branch_labels = None
depends_on = None
# ⚠️ LOGIN-RELATED TABLES — KEIN RLS! Diese Tabellen werden vom Login-Flow
# gelesen bevor ein tenant_id Kontext existiert. RLS blockiert den Login.
# Siehe auch 0108 LOGIN_TABLES und 0085 AUTH_TABLES.
LOGIN_RELATED_TABLES = [
"roles",
"permissions",
]
def _exec(sql: str) -> None:
op.execute(sql)
def upgrade() -> None:
# Disable RLS on login-related tables
for table in LOGIN_RELATED_TABLES:
_exec(
f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables "
f"WHERE table_schema = 'public' AND table_name = '{table}') THEN "
f"DROP POLICY IF EXISTS tenant_isolation ON public.{table}; "
f"DROP POLICY IF EXISTS {table}_tenant_isolation ON public.{table}; "
f"ALTER TABLE public.{table} DISABLE ROW LEVEL SECURITY; "
f"END IF; END $$"
)
# Ensure crm_auth can read roles and permissions (needed for login flow)
for table in LOGIN_RELATED_TABLES:
_exec(
f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables "
f"WHERE table_schema = 'public' AND table_name = '{table}') THEN "
f"GRANT SELECT ON public.{table} TO crm_auth; "
f"END IF; END $$"
)
def downgrade() -> None:
pass