Files
leocrm/alembic/versions/0147_api_tokens_rls_off.py
T

51 lines
1.9 KiB
Python
Raw Normal View History

"""Disable RLS on api_tokens (F08 bootstrap fix, Astra S2).
verify_api_token() must look up the token hash via the request session
(crm_api) BEFORE any tenant context exists — the TOKEN is what determines
the tenant. Forced RLS with a tenant-isolation policy on api_tokens made
that lookup return zero rows, so EVERY Bearer token was rejected with 401
"token_invalid", including freshly created ones (verified live on
production 2026-09-18).
This restores the documented decision from migration 0080 ("written
during login before tenant context") which 0084 inadvertently overrode
by blindly re-enabling fail-closed RLS everywhere. sessions and
password_reset_tokens remain RLS-off for the same bootstrap reason.
Security unchanged: the SHA-256 token hash IS the access secret — a
lookup by hash cannot enumerate other tenants' tokens, and every use of
the row still goes through the authenticated verify path.
Revision ID: 0147
Revises: 0146
"""
from alembic import op
revision = "0147"
down_revision = "0146"
branch_labels = None
depends_on = None
POLICY_NAME = "api_tokens_tenant_isolation"
def upgrade() -> None:
# Remove the tenant-isolation policy first (it only covered the
# runtime roles anyway), then disable + unforce RLS.
op.execute(f"DROP POLICY IF EXISTS {POLICY_NAME} ON api_tokens;")
op.execute("ALTER TABLE api_tokens DISABLE ROW LEVEL SECURITY;")
op.execute("ALTER TABLE api_tokens NO FORCE ROW LEVEL SECURITY;")
def downgrade() -> None:
# Best-effort inverse: restore forced RLS + the previous policy.
op.execute("ALTER TABLE api_tokens ENABLE ROW LEVEL SECURITY;")
op.execute("ALTER TABLE api_tokens FORCE ROW LEVEL SECURITY;")
op.execute(
"CREATE POLICY api_tokens_tenant_isolation ON api_tokens "
"FOR ALL TO crm_api, crm_worker "
"USING (tenant_id = (NULLIF(current_setting('app.current_tenant_id', true), ''))::uuid)"
)