Include full contents of all nested repositories

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-27 16:25:02 +01:00
parent 14ff8fd54c
commit 2401ed446f
7271 changed files with 1310112 additions and 6 deletions

View File

@@ -0,0 +1,19 @@
"""FastAPI dependencies for the Orchestrator."""
from app.dependencies.auth import (
CurrentAgentDep,
get_current_agent,
)
from app.dependencies.admin_auth import AdminAuthDep, verify_admin_api_key
from app.dependencies.dashboard_auth import DashboardAuthDep, verify_dashboard_token
from app.dependencies.local_agent_auth import verify_local_agent_key
__all__ = [
"CurrentAgentDep",
"get_current_agent",
"AdminAuthDep",
"verify_admin_api_key",
"DashboardAuthDep",
"verify_dashboard_token",
"verify_local_agent_key",
]

View File

@@ -0,0 +1,33 @@
"""Admin authentication dependency for protected endpoints."""
import secrets
from fastapi import Depends, Header, HTTPException, status
from app.config import get_settings
async def verify_admin_api_key(
x_admin_api_key: str = Header(..., alias="X-Admin-Api-Key"),
) -> None:
"""
Verify admin API key for protected endpoints.
Used to protect sensitive operations like registration token management.
Raises:
HTTPException: 401 if API key is missing or invalid
"""
settings = get_settings()
# Use timing-safe comparison to prevent timing attacks
if not secrets.compare_digest(x_admin_api_key, settings.ADMIN_API_KEY):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid admin API key",
headers={"WWW-Authenticate": "ApiKey"},
)
# Dependency that can be used in route decorators
AdminAuthDep = Depends(verify_admin_api_key)

View File

@@ -0,0 +1,65 @@
"""Agent authentication dependencies."""
import hashlib
import secrets
import uuid
from typing import Annotated
from fastapi import Depends, Header, HTTPException, status
from sqlalchemy import select
from app.db import AsyncSessionDep
from app.models.agent import Agent
async def get_current_agent(
db: AsyncSessionDep,
x_agent_id: str = Header(..., alias="X-Agent-Id"),
x_agent_secret: str = Header(..., alias="X-Agent-Secret"),
) -> Agent:
"""
Validate agent credentials using X-Agent-Id/X-Agent-Secret headers.
Args:
db: Database session
x_agent_id: Agent UUID from X-Agent-Id header
x_agent_secret: Agent secret from X-Agent-Secret header
Returns:
Agent if credentials are valid
Raises:
HTTPException: 401 if credentials are invalid
"""
# Parse agent ID
try:
agent_id = uuid.UUID(x_agent_id)
except ValueError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid Agent ID format",
)
# Look up agent
result = await db.execute(select(Agent).where(Agent.id == agent_id))
agent = result.scalar_one_or_none()
if agent is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid agent credentials",
)
# Verify secret using timing-safe comparison
provided_hash = hashlib.sha256(x_agent_secret.encode()).hexdigest()
if not secrets.compare_digest(agent.secret_hash, provided_hash):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid agent credentials",
)
return agent
# Type alias for dependency injection
CurrentAgentDep = Annotated[Agent, Depends(get_current_agent)]

View File

@@ -0,0 +1,73 @@
"""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)]

View File

@@ -0,0 +1,49 @@
"""Local agent authentication dependency for LOCAL_MODE registration."""
import secrets
from fastapi import Header, HTTPException, status
from app.config import get_settings
async def verify_local_agent_key(
x_local_agent_key: str = Header(..., alias="X-Local-Agent-Key"),
) -> None:
"""
Verify LOCAL_AGENT_KEY for local agent registration.
This is a narrow-scope credential that can ONLY register the local agent.
It is NOT the same as ADMIN_API_KEY.
HTTP Status Codes:
- 404: LOCAL_MODE is false (endpoint hidden by design)
- 401: LOCAL_AGENT_KEY is missing, invalid, or not configured
Raises:
HTTPException: 404 if not in LOCAL_MODE, 401 if key is invalid
"""
settings = get_settings()
# Endpoint only exists in LOCAL_MODE (security by obscurity)
if not settings.LOCAL_MODE:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Not found",
)
# LOCAL_AGENT_KEY must be configured
if not settings.LOCAL_AGENT_KEY:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Local agent key not configured",
headers={"WWW-Authenticate": "ApiKey"},
)
# Use timing-safe comparison to prevent timing attacks
if not secrets.compare_digest(x_local_agent_key, settings.LOCAL_AGENT_KEY):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid local agent key",
headers={"WWW-Authenticate": "ApiKey"},
)