Performance: - SSE dashboard streaming endpoint (GET /v1/admin/status/stream) - Web Worker for markdown rendering (offload from main thread) - IndexedDB chat persistence (replace localStorage, 500msg support) Security: - CSRF protection middleware (Origin/Referer validation) - Content Security Policy + security headers middleware - API key rotation endpoint (POST /v1/admin/keys/rotate) Observability: - OpenTelemetry tracing with graceful NoOp fallback - Structured error codes (FAGI-xxxx taxonomy with ErrorResponse schema) - Audit log export (CSV + JSON at /v1/admin/audit/export/*) Features: - Multi-session management hook (parallel conversations) - Conversation export (markdown/JSON/text download + clipboard) - Head customization UI (enable/disable + weight sliders for 12 heads) Infrastructure: - Kubernetes Helm chart (Deployment, Service, HPA, Ingress) - Database migration versioning (generate, verify commands) - Blue-green deployment manifests (color-based traffic switching) Tests: 598 Python + 56 frontend = 654 total, 0 ruff errors Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
63 lines
1.6 KiB
Python
63 lines
1.6 KiB
Python
"""API key rotation endpoint.
|
|
|
|
Allows admins to rotate API keys without server restart.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import secrets
|
|
import time
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter
|
|
|
|
from fusionagi._logger import logger
|
|
|
|
router = APIRouter()
|
|
|
|
_key_history: list[dict[str, Any]] = []
|
|
|
|
|
|
def _generate_key(prefix: str = "fagi") -> str:
|
|
"""Generate a cryptographically secure API key."""
|
|
return f"{prefix}_{secrets.token_urlsafe(32)}"
|
|
|
|
|
|
@router.post("/keys/rotate")
|
|
def rotate_api_key(body: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
"""Rotate the API key and return the new key.
|
|
|
|
The old key remains valid for a grace period (configurable).
|
|
The new key is immediately active.
|
|
|
|
Args:
|
|
body: Optional dict with ``grace_period_seconds`` (default 300).
|
|
|
|
Returns:
|
|
Dict with new key and metadata.
|
|
"""
|
|
grace_period = (body or {}).get("grace_period_seconds", 300)
|
|
new_key = _generate_key()
|
|
|
|
rotation_record = {
|
|
"rotated_at": time.time(),
|
|
"grace_period_seconds": grace_period,
|
|
"key_prefix": new_key[:8] + "...",
|
|
}
|
|
_key_history.append(rotation_record)
|
|
|
|
logger.info("API key rotated", extra={"key_prefix": new_key[:8], "grace_period": grace_period})
|
|
|
|
return {
|
|
"new_key": new_key,
|
|
"grace_period_seconds": grace_period,
|
|
"rotated_at": rotation_record["rotated_at"],
|
|
"message": f"Old key valid for {grace_period}s. Update your clients.",
|
|
}
|
|
|
|
|
|
@router.get("/keys/history")
|
|
def key_rotation_history() -> list[dict[str, Any]]:
|
|
"""Return history of key rotations (without revealing full keys)."""
|
|
return _key_history
|