from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jose import JWTError from sqlalchemy.orm import Session from app.auth.security import ( verify_password, get_password_hash, create_tokens, decode_token, ) from app.core.database import get_db from app.models.user import User from app.schemas.token import TokenResponse, RefreshToken from app.schemas.user import UserCreate, UserResponse router = APIRouter() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/login") async def get_current_user( token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_db) ) -> User: """ Get the current user based on the JWT token. Args: token: JWT token from authorization header db: Database session Returns: User object if valid token Raises: HTTPException: If token is invalid or user not found """ try: payload = decode_token(token) if payload.type != "access": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid access token type" ) user = db.query(User).filter(User.id == payload.sub).first() if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found" ) return user except JWTError as e: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Could not validate credentials: {str(e)}" ) async def authenticate_user( email: str, password: str, db: Session ) -> User: """ Authenticate a user by email and password. Args: email: User's email password: User's password db: Database session Returns: User object if authentication successful, None otherwise """ user = db.query(User).filter(User.email == email).first() if not user or not verify_password(password, user.password_hash): return None return user @router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def register( user_create: UserCreate, db: Session = Depends(get_db) ): """ Register a new user. """ # Check if user already exists if db.query(User).filter(User.email == user_create.email).first(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email already registered" ) # Create new user user = User( email=user_create.email, password_hash=get_password_hash(user_create.password), first_name=user_create.first_name, last_name=user_create.last_name, phone_number=user_create.phone_number ) db.add(user) db.commit() db.refresh(user) return user @router.post("/login", response_model=TokenResponse) async def login( form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db: Session = Depends(get_db) ): """ OAuth2 compatible token login, get an access token for future requests. """ user = await authenticate_user(form_data.username, form_data.password, db) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect email or password", headers={"WWW-Authenticate": "Bearer"}, ) if not user.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Inactive user" ) return create_tokens(str(user.id)) @router.post("/refresh", response_model=TokenResponse) async def refresh_token( refresh_token: RefreshToken, db: Session = Depends(get_db) ): """ Refresh access token using refresh token. """ try: payload = decode_token(refresh_token.refresh_token) # Validate token type if payload.type != "refresh": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token type" ) # Verify user still exists and is active user = db.query(User).filter(User.id == payload.sub).first() if not user or not user.is_active: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found or inactive" ) return create_tokens(str(user.id)) except JWTError as e: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail=f"Invalid refresh token: {str(e)}" ) @router.get("/me", response_model=UserResponse) async def read_users_me( current_user: Annotated[User, Depends(get_current_user)] ): """ Get current user information. """ return current_user