LetsBeBiz-Redesign/letsbe-orchestrator/app/dependencies/dashboard_auth.py

74 lines
2.3 KiB
Python
Raw Normal View History

"""Dashboard authentication dependency for tenant dashboard-to-orchestrator communication."""
import hashlib
import secrets
from typing import Annotated
from uuid import UUID
from fastapi import Depends, Header, HTTPException, Path, status
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import get_db
from app.models.tenant import Tenant
async def verify_dashboard_token(
tenant_id: Annotated[UUID, Path(...)],
x_dashboard_token: Annotated[str, Header(alias="X-Dashboard-Token")],
db: AsyncSession = Depends(get_db),
) -> Tenant:
"""
Verify per-tenant dashboard token for tenant dashboard endpoints.
The dashboard token is used by the tenant's dashboard application
(Hub Dashboard or Control Panel) to authenticate requests to the
Orchestrator for task execution and status queries.
Args:
tenant_id: The tenant UUID from the path
x_dashboard_token: The raw dashboard token from header
db: Database session
Returns:
The verified Tenant object
Raises:
HTTPException: 401 if token is missing, invalid, or tenant not found
"""
# Fetch tenant
result = await db.execute(
select(Tenant).where(Tenant.id == tenant_id)
)
tenant = result.scalar_one_or_none()
if not tenant:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Tenant not found",
)
if not tenant.dashboard_token_hash:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Dashboard token not configured for this tenant",
headers={"WWW-Authenticate": "DashboardToken"},
)
# Compute SHA-256 hash of provided token
provided_hash = hashlib.sha256(x_dashboard_token.encode()).hexdigest()
# Use timing-safe comparison to prevent timing attacks
if not secrets.compare_digest(tenant.dashboard_token_hash, provided_hash):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid dashboard token",
headers={"WWW-Authenticate": "DashboardToken"},
)
return tenant
# Type alias for dependency injection
DashboardAuthDep = Annotated[Tenant, Depends(verify_dashboard_token)]