"""Auth middleware — JWT validation dependency. ``get_current_user`` is the FastAPI dependency used by all protected routes. It decodes the Bearer JWT, validates signature and expiry, and returns a ``UserProfile`` carrying ``id``, ``email``, and ``tier``. Exempt routes (no JWT required): - POST /api/v1/auth/register - POST /api/v1/auth/login - POST /api/v1/billing/webhook """ from __future__ import annotations from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from app.config.settings import settings from app.schemas import UserProfile oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login") async def get_current_user( token: str = Depends(oauth2_scheme), ) -> UserProfile: """Validate a Bearer JWT and return the authenticated user. Raises HTTP 401 on any invalid or expired token. The tier embedded in the JWT is used for feature-gating until Step 12 adds a live DB lookup. """ credentials_exc = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode( token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM] ) user_id: str | None = payload.get("sub") email: str | None = payload.get("email") tier: str = payload.get("tier", "free") if not user_id or not email: raise credentials_exc except JWTError: raise credentials_exc return UserProfile(id=user_id, email=email, tier=tier) # type: ignore[arg-type]