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:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user