| """
|
| 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()
|
|
|
|
|
| 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"},
|
| )
|
|
|