""" Simple JWT auth — swap for OAuth2/API keys in production. """ import os from datetime import datetime, timedelta, timezone from typing import Optional from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jose import JWTError, jwt from passlib.context import CryptContext SECRET_KEY = os.getenv("SECRET_KEY", "changeme-in-production-please") ALGORITHM = os.getenv("ALGORITHM", "HS256") EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30")) pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") security = HTTPBearer() # Demo users — replace with real DB lookup USERS: dict[str, str] = { "admin": pwd_context.hash("admin123"), "demo": pwd_context.hash("demo123"), } def verify_password(username: str, password: str) -> bool: hashed = USERS.get(username) if not hashed: return False return pwd_context.verify(password, hashed) def create_access_token(data: dict) -> str: to_encode = data.copy() expire = datetime.now(timezone.utc) + timedelta(minutes=EXPIRE_MINUTES) to_encode["exp"] = expire return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> str: token = credentials.credentials try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: Optional[str] = payload.get("sub") if not username: raise HTTPException(status_code=401, detail="Invalid token payload") return username except JWTError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token", headers={"WWW-Authenticate": "Bearer"}, )