from __future__ import annotations import hmac import secrets from datetime import UTC, datetime, timedelta from typing import Any import jwt from fastapi import Depends, HTTPException, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from server.settings import settings _bearer = HTTPBearer(auto_error=False) ALGORITHM = "HS256" def verify_pin(pin: str) -> bool: return hmac.compare_digest(pin.strip(), settings.app_pin.strip()) def create_token() -> str: expire = datetime.now(UTC) + timedelta(days=settings.jwt_expire_days) payload = {"sub": "user", "exp": expire} return jwt.encode(payload, settings.jwt_secret, algorithm=ALGORITHM) def decode_token(token: str) -> dict[str, Any]: try: return jwt.decode(token, settings.jwt_secret, algorithms=[ALGORITHM]) except jwt.PyJWTError as exc: raise HTTPException(status_code=401, detail="Invalid token") from exc async def require_auth( creds: HTTPAuthorizationCredentials | None = Depends(_bearer), ) -> dict[str, Any]: if creds is None or creds.scheme.lower() != "bearer": raise HTTPException(status_code=401, detail="Missing token") return decode_token(creds.credentials) async def require_agent( creds: HTTPAuthorizationCredentials | None = Depends(_bearer), ) -> dict[str, Any]: """Authenticate external agents via AGENT_TOKEN (long-lived, not JWT).""" if creds is None or creds.scheme.lower() != "bearer": raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing agent token") token = creds.credentials if not settings.agent_token: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Agent API not configured", ) if not secrets.compare_digest(token.strip(), settings.agent_token.strip()): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid agent token") return {"sub": "agent"}