""" Shared tenant middleware for all Compliance API routes. Provides a central FastAPI dependency that resolves tenant_id from: 1. X-Tenant-ID header (primary) 2. Query parameter tenant_id (fallback) 3. Environment variable DEFAULT_TENANT_ID (last resort) UUID validation ensures no more "default" strings leak through. """ import os import re import logging from typing import Optional from fastapi import Header, Query, HTTPException logger = logging.getLogger(__name__) # Fallback for local development — real deployments must pass X-Tenant-ID _ENV_DEFAULT = os.getenv( "DEFAULT_TENANT_ID", "9282a473-5c95-4b3a-bf78-0ecc0ec71d3e" ) _UUID_RE = re.compile( r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.I ) def _validate_tenant_id(tid: str) -> str: """Validate that tenant_id looks like a UUID. Reject 'default' etc.""" if tid == "default": raise HTTPException( status_code=400, detail="Tenant ID 'default' is no longer accepted. Pass a valid UUID.", ) if not _UUID_RE.match(tid): raise HTTPException( status_code=400, detail=f"Invalid tenant_id format: '{tid}'. Must be a UUID.", ) return tid async def get_tenant_id( x_tenant_id: Optional[str] = Header(None, alias="X-Tenant-ID"), tenant_id: Optional[str] = Query(None), ) -> str: """FastAPI dependency — resolves + validates tenant ID. Usage: @router.get("/something") async def my_endpoint(tid: str = Depends(get_tenant_id)): ... """ raw = x_tenant_id or tenant_id or _ENV_DEFAULT return _validate_tenant_id(raw)