""" Enhanced Authentication API Endpoints Updated to use repository pattern with dependency injection and improved error handling """ from fastapi import APIRouter, Depends, HTTPException, status, Request from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import structlog from app.schemas.auth import ( UserRegistration, UserLogin, TokenResponse, RefreshTokenRequest, PasswordChange, PasswordReset, UserResponse ) from app.services.auth_service import EnhancedAuthService from shared.database.base import create_database_manager from shared.monitoring.decorators import track_execution_time from shared.monitoring.metrics import get_metrics_collector from app.core.config import settings logger = structlog.get_logger() router = APIRouter(tags=["enhanced-auth"]) security = HTTPBearer() def get_auth_service(): """Dependency injection for EnhancedAuthService""" database_manager = create_database_manager(settings.DATABASE_URL, "auth-service") return EnhancedAuthService(database_manager) @router.post("/register", response_model=TokenResponse) @track_execution_time("enhanced_registration_duration_seconds", "auth-service") async def register( user_data: UserRegistration, request: Request, auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Register new user using enhanced repository pattern""" metrics = get_metrics_collector(request) logger.info("Registration attempt using repository pattern", email=user_data.email) try: # Enhanced input validation if not user_data.email or not user_data.email.strip(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email is required" ) if not user_data.password or len(user_data.password) < 8: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Password must be at least 8 characters long" ) if not user_data.full_name or not user_data.full_name.strip(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Full name is required" ) # Register user using enhanced service result = await auth_service.register_user(user_data) # Record successful registration if metrics: metrics.increment_counter("enhanced_registration_total", labels={"status": "success"}) logger.info("Registration successful using repository pattern", user_id=result.user.id, email=user_data.email) return result except HTTPException as e: if metrics: error_type = "validation_error" if e.status_code == 400 else "conflict" if e.status_code == 409 else "failed" metrics.increment_counter("enhanced_registration_total", labels={"status": error_type}) logger.warning("Registration failed using repository pattern", email=user_data.email, error=e.detail) raise except Exception as e: if metrics: metrics.increment_counter("enhanced_registration_total", labels={"status": "error"}) logger.error("Registration system error using repository pattern", email=user_data.email, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Registration failed" ) @router.post("/login", response_model=TokenResponse) @track_execution_time("enhanced_login_duration_seconds", "auth-service") async def login( login_data: UserLogin, request: Request, auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Login user using enhanced repository pattern""" metrics = get_metrics_collector(request) logger.info("Login attempt using repository pattern", email=login_data.email) try: # Enhanced input validation if not login_data.email or not login_data.email.strip(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Email is required" ) if not login_data.password: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Password is required" ) # Login using enhanced service result = await auth_service.login_user(login_data) # Record successful login if metrics: metrics.increment_counter("enhanced_login_success_total") logger.info("Login successful using repository pattern", user_id=result.user.id, email=login_data.email) return result except HTTPException as e: if metrics: reason = "validation_error" if e.status_code == 400 else "auth_failed" metrics.increment_counter("enhanced_login_failure_total", labels={"reason": reason}) logger.warning("Login failed using repository pattern", email=login_data.email, error=e.detail) raise except Exception as e: if metrics: metrics.increment_counter("enhanced_login_failure_total", labels={"reason": "error"}) logger.error("Login system error using repository pattern", email=login_data.email, error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Login failed" ) @router.post("/refresh") @track_execution_time("enhanced_token_refresh_duration_seconds", "auth-service") async def refresh_token( refresh_data: RefreshTokenRequest, request: Request, auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Refresh access token using repository pattern""" metrics = get_metrics_collector(request) try: result = await auth_service.refresh_access_token(refresh_data.refresh_token) # Record successful refresh if metrics: metrics.increment_counter("enhanced_token_refresh_success_total") logger.debug("Access token refreshed using repository pattern") return result except HTTPException as e: if metrics: metrics.increment_counter("enhanced_token_refresh_failure_total") logger.warning("Token refresh failed using repository pattern", error=e.detail) raise except Exception as e: if metrics: metrics.increment_counter("enhanced_token_refresh_failure_total") logger.error("Token refresh error using repository pattern", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Token refresh failed" ) @router.post("/verify") @track_execution_time("enhanced_token_verify_duration_seconds", "auth-service") async def verify_token( credentials: HTTPAuthorizationCredentials = Depends(security), request: Request = None, auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Verify access token using repository pattern""" metrics = get_metrics_collector(request) if request else None try: if not credentials: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required" ) result = await auth_service.verify_user_token(credentials.credentials) # Record successful verification if metrics: metrics.increment_counter("enhanced_token_verify_success_total") return { "valid": True, "user_id": result.get("user_id"), "email": result.get("email"), "role": result.get("role"), "exp": result.get("exp"), "message": None } except HTTPException as e: if metrics: metrics.increment_counter("enhanced_token_verify_failure_total") logger.warning("Token verification failed using repository pattern", error=e.detail) raise except Exception as e: if metrics: metrics.increment_counter("enhanced_token_verify_failure_total") logger.error("Token verification error using repository pattern", error=str(e)) raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) @router.post("/logout") @track_execution_time("enhanced_logout_duration_seconds", "auth-service") async def logout( refresh_data: RefreshTokenRequest, request: Request, credentials: HTTPAuthorizationCredentials = Depends(security), auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Logout user using repository pattern""" metrics = get_metrics_collector(request) try: # Verify token to get user_id payload = await auth_service.verify_user_token(credentials.credentials) user_id = payload.get("user_id") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) success = await auth_service.logout_user(user_id, refresh_data.refresh_token) if metrics: status_label = "success" if success else "failed" metrics.increment_counter("enhanced_logout_total", labels={"status": status_label}) logger.info("Logout using repository pattern", user_id=user_id, success=success) return {"message": "Logout successful" if success else "Logout failed"} except HTTPException: raise except Exception as e: if metrics: metrics.increment_counter("enhanced_logout_total", labels={"status": "error"}) logger.error("Logout error using repository pattern", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Logout failed" ) @router.post("/change-password") async def change_password( password_data: PasswordChange, credentials: HTTPAuthorizationCredentials = Depends(security), request: Request = None, auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Change user password using repository pattern""" metrics = get_metrics_collector(request) if request else None try: if not credentials: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required" ) # Verify current token payload = await auth_service.verify_user_token(credentials.credentials) user_id = payload.get("user_id") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) # Validate new password length if len(password_data.new_password) < 8: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="New password must be at least 8 characters long" ) # Change password using enhanced service success = await auth_service.change_password( user_id, password_data.current_password, password_data.new_password ) if metrics: status_label = "success" if success else "failed" metrics.increment_counter("enhanced_password_change_total", labels={"status": status_label}) logger.info("Password changed using repository pattern", user_id=user_id, success=success) return {"message": "Password changed successfully"} except HTTPException: raise except Exception as e: if metrics: metrics.increment_counter("enhanced_password_change_total", labels={"status": "error"}) logger.error("Password change error using repository pattern", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Password change failed" ) @router.get("/profile", response_model=UserResponse) async def get_profile( credentials: HTTPAuthorizationCredentials = Depends(security), auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Get user profile using repository pattern""" try: if not credentials: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required" ) # Verify token and get user_id payload = await auth_service.verify_user_token(credentials.credentials) user_id = payload.get("user_id") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) # Get user profile using enhanced service profile = await auth_service.get_user_profile(user_id) if not profile: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User profile not found" ) return profile except HTTPException: raise except Exception as e: logger.error("Get profile error using repository pattern", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to get profile" ) @router.put("/profile", response_model=UserResponse) async def update_profile( update_data: dict, credentials: HTTPAuthorizationCredentials = Depends(security), auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Update user profile using repository pattern""" try: if not credentials: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required" ) # Verify token and get user_id payload = await auth_service.verify_user_token(credentials.credentials) user_id = payload.get("user_id") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) # Update profile using enhanced service updated_profile = await auth_service.update_user_profile(user_id, update_data) if not updated_profile: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found" ) logger.info("Profile updated using repository pattern", user_id=user_id, updated_fields=list(update_data.keys())) return updated_profile except HTTPException: raise except Exception as e: logger.error("Update profile error using repository pattern", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to update profile" ) @router.post("/verify-email") async def verify_email( user_id: str, verification_token: str, auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Verify user email using repository pattern""" try: success = await auth_service.verify_user_email(user_id, verification_token) logger.info("Email verification using repository pattern", user_id=user_id, success=success) return {"message": "Email verified successfully" if success else "Email verification failed"} except Exception as e: logger.error("Email verification error using repository pattern", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Email verification failed" ) @router.post("/reset-password") async def reset_password( reset_data: PasswordReset, request: Request, auth_service: EnhancedAuthService = Depends(get_auth_service) ): """Request password reset using repository pattern""" metrics = get_metrics_collector(request) try: # In a full implementation, you'd send an email with a reset token # For now, just log the request if metrics: metrics.increment_counter("enhanced_password_reset_total", labels={"status": "requested"}) logger.info("Password reset requested using repository pattern", email=reset_data.email) return {"message": "Password reset email sent if account exists"} except Exception as e: if metrics: metrics.increment_counter("enhanced_password_reset_total", labels={"status": "error"}) logger.error("Password reset error using repository pattern", error=str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Password reset failed" ) @router.get("/health") async def health_check(): """Health check endpoint for enhanced auth service""" return { "status": "healthy", "service": "enhanced-auth-service", "version": "2.0.0", "features": ["repository-pattern", "dependency-injection", "enhanced-error-handling"] }