File size: 2,902 Bytes
b09b8a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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)


@lru_cache(maxsize=1)
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.",
        )