"""Authentication module for Dangerous Pi API.""" import secrets from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBasic, HTTPBasicCredentials from .. import config security = HTTPBasic() def verify_credentials(credentials: HTTPBasicCredentials = Depends(security)) -> str: """Verify HTTP Basic Auth credentials. Args: credentials: HTTP Basic credentials from request Returns: Username if authentication successful Raises: HTTPException: If authentication fails """ if not config.AUTH_ENABLED: return "anonymous" if not config.AUTH_PASSWORD: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="AUTH_ENABLED is true but AUTH_PASSWORD is not set", ) # Use constant-time comparison to prevent timing attacks is_correct_username = secrets.compare_digest( credentials.username.encode("utf-8"), config.AUTH_USERNAME.encode("utf-8") ) is_correct_password = secrets.compare_digest( credentials.password.encode("utf-8"), config.AUTH_PASSWORD.encode("utf-8") ) if not (is_correct_username and is_correct_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials", headers={"WWW-Authenticate": "Basic"}, ) return credentials.username def get_optional_auth(credentials: HTTPBasicCredentials = Depends(security)) -> str | None: """Optional authentication - returns username or None. Use this for endpoints where auth is optional based on config. """ if not config.AUTH_ENABLED: return None try: return verify_credentials(credentials) except HTTPException: return None