"""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)" )