# app/api/dependencies.py import logging from typing import Optional from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from sqlalchemy.ext.asyncio import AsyncSession from jose import JWTError from app.database import get_db from app.core.security import verify_access_token from app.crud import user as crud_user from app.models import User as UserModel # Import the SQLAlchemy model logger = logging.getLogger(__name__) # Define the OAuth2 scheme # tokenUrl should point to your login endpoint relative to the base path # It's used by Swagger UI for the "Authorize" button flow. oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login") # Corrected path async def get_current_user( token: str = Depends(oauth2_scheme), db: AsyncSession = Depends(get_db) ) -> UserModel: """ Dependency to get the current user based on the JWT token. - Extracts token using OAuth2PasswordBearer. - Verifies the token (signature, expiry). - Fetches the user from the database based on the token's subject (email). - Raises HTTPException 401 if any step fails. Returns: The authenticated user's database model instance. """ credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) payload = verify_access_token(token) if payload is None: logger.warning("Token verification failed (invalid, expired, or malformed).") raise credentials_exception email: Optional[str] = payload.get("sub") if email is None: logger.error("Token payload missing 'sub' (subject/email).") raise credentials_exception # Token is malformed # Fetch user from database user = await crud_user.get_user_by_email(db, email=email) if user is None: logger.warning(f"User corresponding to token subject not found: {email}") # Could happen if user deleted after token issuance raise credentials_exception # Treat as invalid credentials logger.debug(f"Authenticated user retrieved: {user.email} (ID: {user.id})") return user # Optional: Dependency for getting the *active* current user # You might add an `is_active` flag to your User model later # async def get_current_active_user( # current_user: UserModel = Depends(get_current_user) # ) -> UserModel: # if not current_user.is_active: # Assuming an is_active attribute # logger.warning(f"Authentication attempt by inactive user: {current_user.email}") # raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user") # return current_user