fix(migration): exclude login tables from RLS in 0108 — RLS blocks crm_auth login

LOGIN_TABLES (users, user_tenants, tenants, sessions, password_reset_tokens)
added to skip list. RLS on these tables blocked crm_auth from reading users
during login → 401 Invalid email or password.

crm_auth grants applied directly (no RLS) matching 0085 AUTH_TABLES.

⚠️ LOGIN-TABELLEN DÜRFEN KEIN RLS BEKOMMEN — RLS blockiert crm_auth beim Login.
Siehe 0085 AUTH_TABLES für die korrekten Grants.
This commit is contained in:
Agent Zero
2026-08-06 00:56:19 +02:00
parent 0ae8db4932
commit 6631615bef
@@ -15,7 +15,10 @@ has a ``tenant_id`` column but does **not** yet have RLS enabled, then:
5. Grants the appropriate permissions to ``crm_auth`` on login tables 5. Grants the appropriate permissions to ``crm_auth`` on login tables
(users, user_tenants, tenants, sessions, password_reset_tokens). (users, user_tenants, tenants, sessions, password_reset_tokens).
Global tables without tenant isolation requirements are skipped. Global tables and login tables without tenant isolation requirements are skipped.
⚠️ LOGIN-TABELLEN DÜRFEN KEIN RLS BEKOMMEN — RLS blockiert crm_auth beim Login.
Siehe 0085 AUTH_TABLES für die korrekten Grants.
Revision ID: 0108 Revision ID: 0108
Revises: 0107 Revises: 0107
@@ -40,8 +43,20 @@ GLOBAL_TABLES = [
"notification_types", "notification_types",
] ]
# Login / identity tables that DO have tenant_id and need RLS, but also need # ⚠️ LOGIN-TABELLEN DÜRFEN KEIN RLS BEKOMMEN — RLS blockiert crm_auth beim Login.
# crm_auth to keep the permissions defined in migration 0085. # Siehe 0085 AUTH_TABLES für die korrekten Grants.
# These tables have tenant_id but must NOT get RLS because crm_auth (the login
# role) is not included in the RLS policy. RLS on these tables blocks the
# login flow (crm_auth cannot read users → 401 Invalid email or password).
LOGIN_TABLES = [
"users",
"user_tenants",
"tenants",
"sessions",
"password_reset_tokens",
]
# Permissions that crm_auth needs on login tables (matching 0085 AUTH_TABLES)
AUTH_TABLES = { AUTH_TABLES = {
"users": ["SELECT"], "users": ["SELECT"],
"user_tenants": ["SELECT"], "user_tenants": ["SELECT"],
@@ -58,7 +73,7 @@ def _exec(sql: str) -> None:
def upgrade() -> None: def upgrade() -> None:
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# Dynamic discovery + RLS activation for every tenant table that # # Dynamic discovery + RLS activation for every tenant table that #
# was created after migration 0085 and therefore lacks RLS. # # was created after migration 0085 and therefore lacks RLS. #
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
_exec(""" _exec("""
@@ -66,7 +81,6 @@ DO $$
DECLARE DECLARE
r RECORD; r RECORD;
policy_sql TEXT; policy_sql TEXT;
auth_sql TEXT;
BEGIN BEGIN
FOR r IN FOR r IN
SELECT t.table_name SELECT t.table_name
@@ -82,7 +96,14 @@ BEGIN
'plugin_migrations', 'plugin_migrations',
'marketplace_listings', 'marketplace_listings',
'sequences', 'sequences',
'notification_types' 'notification_types',
-- ⚠️ LOGIN-TABELLEN DÜRFEN KEIN RLS BEKOMMEN — RLS blockiert
-- crm_auth beim Login. Siehe 0085 AUTH_TABLES.
'users',
'user_tenants',
'tenants',
'sessions',
'password_reset_tokens'
) )
AND NOT EXISTS ( AND NOT EXISTS (
SELECT 1 SELECT 1
@@ -116,23 +137,23 @@ BEGIN
-- Grant CRUD to crm_api and crm_worker -- Grant CRUD to crm_api and crm_worker
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO crm_api', r.table_name); EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO crm_api', r.table_name);
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO crm_worker', r.table_name); EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON public.%I TO crm_worker', r.table_name);
-- Grant crm_auth permissions on login tables (matching 0085 AUTH_TABLES)
IF r.table_name = 'users' THEN
GRANT SELECT ON public.users TO crm_auth;
ELSIF r.table_name = 'user_tenants' THEN
GRANT SELECT ON public.user_tenants TO crm_auth;
ELSIF r.table_name = 'tenants' THEN
GRANT SELECT ON public.tenants TO crm_auth;
ELSIF r.table_name = 'password_reset_tokens' THEN
GRANT SELECT, INSERT, UPDATE, DELETE ON public.password_reset_tokens TO crm_auth;
ELSIF r.table_name = 'sessions' THEN
GRANT SELECT, INSERT, UPDATE, DELETE ON public.sessions TO crm_auth;
END IF;
END LOOP; END LOOP;
END $$; END $$;
""") """)
# ------------------------------------------------------------------ #
# crm_auth grants on login tables (NO RLS on these tables!) #
# ⚠️ LOGIN-TABELLEN DÜRFEN KEIN RLS BEKOMMEN — RLS blockiert #
# crm_auth beim Login. Siehe 0085 AUTH_TABLES für die korrekten Grants.#
# ------------------------------------------------------------------ #
for table, privs in AUTH_TABLES.items():
priv_str = ", ".join(privs)
_exec(
f"DO $$ BEGIN IF EXISTS (SELECT 1 FROM information_schema.tables "
f"WHERE table_schema = 'public' AND table_name = '{table}') "
f"THEN GRANT {priv_str} ON public.{table} TO crm_auth; END IF; END $$"
)
def downgrade() -> None: def downgrade() -> None:
pass pass