fix: guest_sessions Redis index for revocation + RLS on all tenant tables (migration 0064)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
"""Enable RLS on all remaining tenant tables.
|
||||
|
||||
Revision ID: 0064
|
||||
Revises: 0063
|
||||
Create Date: 2026-07-29
|
||||
|
||||
Currently RLS is only on contacts. This migration enables RLS on all
|
||||
tenant-scoped tables that have a tenant_id column but no RLS yet.
|
||||
|
||||
System tables (users, tenants, groups, roles) are excluded — they need
|
||||
special handling for the login bootstrap process.
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "0064"
|
||||
down_revision = "0063"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
# Tables that should have RLS (tenant-scoped data)
|
||||
TENANT_TABLES = [
|
||||
"addresses",
|
||||
"attachments",
|
||||
"bank_accounts",
|
||||
"contact_folders",
|
||||
"contact_merge_history",
|
||||
"workflows",
|
||||
"sequences",
|
||||
"saved_filters",
|
||||
"saved_views",
|
||||
"webhooks",
|
||||
"custom_field_definitions",
|
||||
"notifications",
|
||||
"ai_conversations",
|
||||
"contact_persons",
|
||||
"tags",
|
||||
"entity_links",
|
||||
"dms_files",
|
||||
"dms_folders",
|
||||
"calendar_events",
|
||||
"calendars",
|
||||
"tasks",
|
||||
"task_lists",
|
||||
"mail_messages",
|
||||
"mail_accounts",
|
||||
"mail_folders",
|
||||
"conversations",
|
||||
"conversation_messages",
|
||||
"conversation_participants",
|
||||
"audit_log",
|
||||
"permission_delegations",
|
||||
"guest_invitations",
|
||||
]
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
for table in TENANT_TABLES:
|
||||
# Enable RLS if not already enabled
|
||||
op.execute(f"""
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_class c
|
||||
WHERE c.relname = '{table}'
|
||||
AND c.relrowsecurity = true
|
||||
) AND EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '{table}'
|
||||
AND column_name = 'tenant_id'
|
||||
) THEN
|
||||
ALTER TABLE {table} ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY {table}_tenant_isolation ON {table}
|
||||
FOR ALL
|
||||
USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid)
|
||||
WITH CHECK (tenant_id = current_setting('app.current_tenant_id', true)::uuid);
|
||||
END IF;
|
||||
END $$;
|
||||
""")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
for table in TENANT_TABLES:
|
||||
op.execute(f"DROP POLICY IF EXISTS {table}_tenant_isolation ON {table}")
|
||||
op.execute(f"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY")
|
||||
@@ -112,6 +112,9 @@ async def guest_login(
|
||||
1800, # 30 min TTL
|
||||
json.dumps(session_data),
|
||||
)
|
||||
# Track session in guest index for revocation (P1.6 fix)
|
||||
await redis.sadd(f"guest_sessions:{guest.id}", session_id)
|
||||
await redis.expire(f"guest_sessions:{guest.id}", 1800)
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
@@ -145,6 +148,14 @@ async def guest_logout(
|
||||
session_id = request.cookies.get("guest_session")
|
||||
if session_id:
|
||||
redis = get_redis()
|
||||
# Remove from guest sessions index (P1.6 fix)
|
||||
guest_data = await redis.get(f"guest_session:{session_id}")
|
||||
if guest_data:
|
||||
import json
|
||||
data = json.loads(guest_data)
|
||||
gid = data.get("guest_user_id")
|
||||
if gid:
|
||||
await redis.srem(f"guest_sessions:{gid}", session_id)
|
||||
await redis.delete(f"guest_session:{session_id}")
|
||||
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
Reference in New Issue
Block a user