Files
smart-support/backend/app/auth.py
Yaojia Wang af53111928 refactor: fix architectural issues across frontend and backend
Address all architecture review findings:

P0 fixes:
- Add API key authentication for admin endpoints (analytics, replay, openapi)
  and WebSocket connections via ADMIN_API_KEY env var
- Add PostgreSQL-backed PgSessionManager and PgInterruptManager for
  multi-worker production deployments (in-memory defaults preserved)

P1 fixes:
- Implement actual tool generation in OpenAPI approve_job endpoint
  using generate_tool_code() and generate_agent_yaml()
- Add missing clarification, interrupt_expired, and tool_result message
  handlers in frontend ChatPage

P2 fixes:
- Replace monkey-patching on CompiledStateGraph with typed GraphContext
- Replace 9-param dispatch_message with WebSocketContext dataclass
- Extract duplicate _envelope() into shared app/api_utils.py
- Replace mutable module-level counter with crypto.randomUUID()
- Remove hardcoded mock data from ReviewPage, use api.ts wrappers
- Remove `as any` type escape from ReplayPage

All 516 tests passing, 0 TypeScript errors.
2026-04-06 15:59:14 +02:00

73 lines
2.1 KiB
Python

"""API key authentication for admin endpoints and WebSocket connections."""
from __future__ import annotations
import logging
import secrets
from typing import Annotated
from fastapi import Depends, HTTPException, Query, Request, WebSocket, status
from fastapi.security import APIKeyHeader
logger = logging.getLogger(__name__)
_API_KEY_HEADER = APIKeyHeader(name="X-API-Key", auto_error=False)
def _get_admin_api_key(request: Request) -> str:
"""Retrieve the configured admin API key from app settings.
Returns empty string if settings are not configured (test/dev mode).
"""
settings = getattr(request.app.state, "settings", None)
if settings is None:
return ""
key = getattr(settings, "admin_api_key", "")
return key if isinstance(key, str) else ""
async def require_admin_api_key(
request: Request,
api_key: Annotated[str | None, Depends(_API_KEY_HEADER)] = None,
) -> None:
"""Dependency that enforces API key authentication on admin endpoints.
Skips validation when no admin_api_key is configured (dev mode).
"""
expected = _get_admin_api_key(request)
if not expected:
return
if api_key is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Missing X-API-Key header",
)
if not secrets.compare_digest(api_key, expected):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid API key",
)
async def verify_ws_token(
ws: WebSocket,
token: str | None = Query(default=None),
) -> None:
"""Verify WebSocket connection token from query parameter.
Skips validation when no admin_api_key is configured (dev mode).
Usage: ws://host/ws?token=<api_key>
"""
settings = ws.app.state.settings
expected = settings.admin_api_key
if not expected:
return
if token is None or not secrets.compare_digest(token, expected):
await ws.close(code=4001, reason="Unauthorized")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid or missing WebSocket token",
)