74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
"""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)]
|