from datetime import timedelta from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm from motor.motor_asyncio import AsyncIOMotorDatabase from ..schemas.auth import UserRegister, Token, TokenRefresh, UserResponse from ..services.user_service import UserService from ..db.mongodb import get_database from ..core.security import ( create_access_token, create_refresh_token, decode_token, get_current_user_id ) from ..core.config import settings router = APIRouter(prefix="/api/auth", tags=["authentication"]) def get_user_service(db: AsyncIOMotorDatabase = Depends(get_database)) -> UserService: """Dependency to get user service""" return UserService(db) @router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def register( user_data: UserRegister, user_service: UserService = Depends(get_user_service) ): """Register a new user""" user = await user_service.create_user(user_data) return UserResponse( _id=str(user.id), email=user.email, username=user.username, full_name=user.full_name, role=user.role, permissions=user.permissions, status=user.status, is_active=user.is_active, created_at=user.created_at.isoformat(), last_login_at=user.last_login_at.isoformat() if user.last_login_at else None ) @router.post("/login", response_model=Token) async def login( form_data: OAuth2PasswordRequestForm = Depends(), user_service: UserService = Depends(get_user_service) ): """Login with username/email and password""" user = await user_service.authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username/email or password", headers={"WWW-Authenticate": "Bearer"}, ) # Update last login timestamp await user_service.update_last_login(str(user.id)) # Create tokens access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": str(user.id), "username": user.username}, expires_delta=access_token_expires ) refresh_token = create_refresh_token(data={"sub": str(user.id)}) return Token( access_token=access_token, refresh_token=refresh_token, token_type="bearer" ) @router.post("/refresh", response_model=Token) async def refresh_token( token_data: TokenRefresh, user_service: UserService = Depends(get_user_service) ): """Refresh access token using refresh token""" try: payload = decode_token(token_data.refresh_token) # Verify it's a refresh token if payload.get("type") != "refresh": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token type" ) user_id = payload.get("sub") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) # Verify user still exists and is active user = await user_service.get_user_by_id(user_id) if not user or not user.is_active: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found or inactive" ) # Create new access token access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user_id, "username": user.username}, expires_delta=access_token_expires ) return Token( access_token=access_token, refresh_token=token_data.refresh_token, token_type="bearer" ) except Exception as e: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired refresh token" ) @router.get("/me", response_model=UserResponse) async def get_current_user( user_id: str = Depends(get_current_user_id), user_service: UserService = Depends(get_user_service) ): """Get current user information""" user = await user_service.get_user_by_id(user_id) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) return UserResponse( _id=str(user.id), email=user.email, username=user.username, full_name=user.full_name, role=user.role, permissions=user.permissions, status=user.status, is_active=user.is_active, created_at=user.created_at.isoformat(), last_login_at=user.last_login_at.isoformat() if user.last_login_at else None ) @router.post("/logout") async def logout(user_id: str = Depends(get_current_user_id)): """Logout endpoint (token should be removed on client side)""" # In a more sophisticated system, you might want to: # 1. Blacklist the token in Redis # 2. Log the logout event # 3. Clear any session data return {"message": "Successfully logged out"}