from __future__ import annotations from datetime import datetime, timedelta, timezone from fastapi import Header, HTTPException, status from jose import JWTError, jwt from passlib.context import CryptContext from app.config import settings pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_DAYS = 7 def verify_password(plain: str, hashed: str) -> bool: return pwd_context.verify(plain, hashed) def get_password_hash(password: str) -> str: return pwd_context.hash(password) def create_access_token(data: dict) -> str: to_encode = data.copy() expire = datetime.now(timezone.utc) + timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS) to_encode.update({"exp": expire, "iat": datetime.now(timezone.utc)}) return jwt.encode(to_encode, settings.JWT_SECRET, algorithm=ALGORITHM) def decode_token(token: str) -> dict | None: try: return jwt.decode(token, settings.JWT_SECRET, algorithms=[ALGORITHM]) except JWTError: return None async def get_api_key(x_api_key: str | None = Header(None)) -> str | None: if x_api_key and x_api_key == settings.API_KEY: return x_api_key return None async def require_auth( authorization: str | None = Header(None), x_api_key: str | None = Header(None), ) -> dict: if x_api_key and x_api_key == settings.API_KEY: return {"auth_type": "api_key"} if authorization: scheme, _, token = authorization.partition(" ") if scheme.lower() == "bearer" and token: payload = decode_token(token) if payload: return {"auth_type": "jwt", **payload} if x_api_key == settings.API_KEY: return {"auth_type": "api_key"} raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or missing authentication credentials", headers={"WWW-Authenticate": "Bearer"}, )