Spaces:
Sleeping
Sleeping
| import os | |
| from functools import lru_cache | |
| from typing import Optional | |
| from fastapi import HTTPException, Security, status | |
| from fastapi.security import APIKeyHeader | |
| from app.core.logging import get_logger | |
| logger = get_logger(__name__) | |
| api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False) | |
| def _get_configured_api_key() -> Optional[str]: | |
| """Return the configured API key, or None if not set. | |
| The key is read from the API_KEY environment variable. | |
| """ | |
| raw = os.getenv("API_KEY") | |
| if raw is None or not raw.strip(): | |
| return None | |
| return raw.strip() | |
| def _is_production_like() -> bool: | |
| """Heuristic to detect production / hosted environments. | |
| - ENV=production | |
| - or running on Hugging Face Spaces (SPACE_ID or HF_HOME set) | |
| """ | |
| env = os.getenv("ENV", "").strip().lower() | |
| if env == "production": | |
| return True | |
| if os.getenv("SPACE_ID") or os.getenv("HF_HOME"): | |
| return True | |
| return False | |
| def validate_api_key_configuration() -> None: | |
| """Validate API key configuration at startup. | |
| Behaviour: | |
| - In production-like environments (HF Spaces or ENV=production): | |
| - API_KEY MUST be set, otherwise raise RuntimeError (fail fast). | |
| - In other environments: | |
| - If API_KEY is missing, allow running open but log a clear warning. | |
| """ | |
| configured = _get_configured_api_key() | |
| if _is_production_like(): | |
| if not configured: | |
| raise RuntimeError( | |
| "API_KEY environment variable must be set when running in " | |
| "production or on Hugging Face Spaces. Configure API_KEY in " | |
| "your environment or Space secrets." | |
| ) | |
| logger.info("API key configured for production / hosted environment.") | |
| else: | |
| if not configured: | |
| logger.warning( | |
| "API_KEY is not set; backend is running without authentication. " | |
| "This is intended for local development only." | |
| ) | |
| else: | |
| logger.info("API key configured for development mode.") | |
| async def require_api_key(api_key: Optional[str] = Security(api_key_header)) -> None: | |
| """FastAPI dependency that enforces X-API-Key when configured. | |
| - If API_KEY is not configured (local/dev), this is a no-op. | |
| - If API_KEY is configured: | |
| - Missing or mismatched X-API-Key results in HTTP 403. | |
| """ | |
| configured = _get_configured_api_key() | |
| if not configured: | |
| # No API key configured: dev mode, do not enforce. | |
| return | |
| if not api_key: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Missing API key. Provide X-API-Key header.", | |
| ) | |
| if api_key != configured: | |
| raise HTTPException( | |
| status_code=status.HTTP_403_FORBIDDEN, | |
| detail="Invalid API key.", | |
| ) |