"""Auth dependencies — JWT validation for the Auth Service. This is the canonical get_current_user used by protected endpoints within the Auth Service itself (/me, /me PUT). """ from __future__ import annotations from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from shared.config import settings from shared.db import get_session from shared.models import Subscription, User from shared.schemas import UserProfile oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login") async def get_current_user( token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_session), ) -> UserProfile: """Validate a Bearer JWT and return the authenticated user. The JWT is used for identity and expiry. Tier is fetched live from the subscriptions table so upgrades/downgrades take effect immediately. """ 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") if not user_id or not email: raise credentials_exc except JWTError: raise credentials_exc # Live tier lookup result = await db.execute( select(Subscription.tier).where(Subscription.user_id == user_id) ) default_tier = "power" if settings.ENV == "dev" else "free" tier: str = result.scalar_one_or_none() or default_tier # Fetch name/surname user_result = await db.execute( select(User.name, User.surname).where(User.id == user_id) ) user_row = user_result.one_or_none() return UserProfile( id=user_id, email=email, name=user_row.name if user_row else None, surname=user_row.surname if user_row else None, tier=tier, ) # type: ignore[arg-type]