Coverage for src/semware/api/auth.py: 64%
33 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-09 02:16 -0700
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-09 02:16 -0700
1"""API Key authentication for SemWare."""
3from fastapi import HTTPException, Request, status
4from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
5from loguru import logger
7from ..config import settings
10class APIKeyBearer(HTTPBearer):
11 """Custom HTTPBearer for API key authentication."""
13 def __init__(self, auto_error: bool = False):
14 super().__init__(auto_error=auto_error)
16 async def __call__(self, request: Request) -> str:
17 """Authenticate API key from Authorization header or X-API-Key header.
19 Args:
20 request: FastAPI request object
22 Returns:
23 API key if valid
25 Raises:
26 HTTPException: If authentication fails
27 """
28 # Try Authorization header first
29 credentials: HTTPAuthorizationCredentials = await super().__call__(request)
30 if credentials and credentials.scheme == "Bearer":
31 api_key = credentials.credentials
32 else:
33 # Try X-API-Key header
34 api_key = request.headers.get("X-API-Key")
36 if not api_key:
37 logger.warning("No API key provided in request")
38 raise HTTPException(
39 status_code=status.HTTP_401_UNAUTHORIZED,
40 detail="API key required. Provide it via 'Authorization: Bearer <key>' or 'X-API-Key: <key>' header",
41 headers={"WWW-Authenticate": "Bearer"},
42 )
44 if not self.verify_api_key(api_key):
45 logger.warning(f"Invalid API key provided: {api_key[:8]}...")
46 raise HTTPException(
47 status_code=status.HTTP_401_UNAUTHORIZED,
48 detail="Invalid API key",
49 headers={"WWW-Authenticate": "Bearer"},
50 )
52 logger.debug("API key authentication successful")
53 return api_key
55 def verify_api_key(self, api_key: str) -> bool:
56 """Verify if the provided API key is valid.
58 Args:
59 api_key: API key to verify
61 Returns:
62 True if valid, False otherwise
63 """
64 return api_key == settings.api_key
67# Global API key authentication instance
68api_key_auth = APIKeyBearer()
71# Alternative header-based authentication for convenience
72async def get_api_key_from_header(request: Request) -> str:
73 """Get API key from X-API-Key header.
75 Args:
76 request: FastAPI request object
78 Returns:
79 API key if valid
81 Raises:
82 HTTPException: If authentication fails
83 """
84 api_key = request.headers.get("X-API-Key")
86 if not api_key:
87 logger.warning("No X-API-Key header provided")
88 raise HTTPException(
89 status_code=status.HTTP_401_UNAUTHORIZED,
90 detail="API key required in X-API-Key header",
91 )
93 if api_key != settings.api_key:
94 logger.warning(f"Invalid API key in X-API-Key header: {api_key[:8]}...")
95 raise HTTPException(
96 status_code=status.HTTP_401_UNAUTHORIZED,
97 detail="Invalid API key",
98 )
100 logger.debug("X-API-Key authentication successful")
101 return api_key