REFACTOR - Database logic

This commit is contained in:
Urtzi Alfaro
2025-08-08 09:08:41 +02:00
parent 0154365bfc
commit 488bb3ef93
113 changed files with 22842 additions and 6503 deletions

View File

@@ -1,41 +1,48 @@
# services/auth/app/api/auth.py - Fixed Login Method
"""
Authentication API endpoints - FIXED VERSION
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
from sqlalchemy.ext.asyncio import AsyncSession
import structlog
from app.core.database import get_db
from app.core.security import SecurityManager
from app.services.auth_service import AuthService
from app.schemas.auth import PasswordReset, UserRegistration, UserLogin, TokenResponse, RefreshTokenRequest, PasswordChange
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()
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("registration_duration_seconds", "auth-service")
@track_execution_time("enhanced_registration_duration_seconds", "auth-service")
async def register(
user_data: UserRegistration,
request: Request,
db: AsyncSession = Depends(get_db)
auth_service: EnhancedAuthService = Depends(get_auth_service)
):
"""Register new user with enhanced debugging"""
"""Register new user using enhanced repository pattern"""
metrics = get_metrics_collector(request)
# ✅ DEBUG: Log incoming registration data (without password)
logger.info(f"Registration attempt for email: {user_data.email}")
logger.debug(f"Registration data - email: {user_data.email}, full_name: {user_data.full_name}, role: {user_data.role}")
logger.info("Registration attempt using repository pattern",
email=user_data.email)
try:
# ✅ DEBUG: Validate input data
# Enhanced input validation
if not user_data.email or not user_data.email.strip():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -54,65 +61,58 @@ async def register(
detail="Full name is required"
)
logger.debug(f"Input validation passed for {user_data.email}")
result = await AuthService.register_user(user_data, db)
logger.info(f"Registration successful for {user_data.email}")
# Register user using enhanced service
result = await auth_service.register_user(user_data)
# Record successful registration
if metrics:
metrics.increment_counter("registration_total", labels={"status": "success"})
metrics.increment_counter("enhanced_registration_total", labels={"status": "success"})
# ✅ DEBUG: Validate response before returning
if not result.get("access_token"):
logger.error(f"Registration succeeded but no access_token in result for {user_data.email}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Registration completed but token generation failed"
)
logger.debug(f"Returning token response for {user_data.email}")
return TokenResponse(**result)
logger.info("Registration successful using repository pattern",
user_id=result.user.id,
email=user_data.email)
return result
except HTTPException as e:
# Record failed registration with specific error
if metrics:
error_type = "validation_error" if e.status_code == 400 else "conflict" if e.status_code == 409 else "failed"
metrics.increment_counter("registration_total", labels={"status": error_type})
metrics.increment_counter("enhanced_registration_total", labels={"status": error_type})
logger.warning(f"Registration failed for {user_data.email}: {e.detail}")
logger.warning("Registration failed using repository pattern",
email=user_data.email,
error=e.detail)
raise
except Exception as e:
# Record registration system error
if metrics:
metrics.increment_counter("registration_total", labels={"status": "error"})
metrics.increment_counter("enhanced_registration_total", labels={"status": "error"})
logger.error(f"Registration system error for {user_data.email}: {str(e)}", exc_info=True)
# ✅ DEBUG: Provide more specific error information in development
error_detail = f"Registration failed: {str(e)}" if logger.level == "DEBUG" else "Registration failed"
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=error_detail
detail="Registration failed"
)
@router.post("/login", response_model=TokenResponse)
@track_execution_time("login_duration_seconds", "auth-service")
@track_execution_time("enhanced_login_duration_seconds", "auth-service")
async def login(
login_data: UserLogin,
request: Request,
db: AsyncSession = Depends(get_db)
auth_service: EnhancedAuthService = Depends(get_auth_service)
):
"""Login user with enhanced debugging"""
"""Login user using enhanced repository pattern"""
metrics = get_metrics_collector(request)
logger.info(f"Login attempt for email: {login_data.email}")
logger.info("Login attempt using repository pattern",
email=login_data.email)
try:
# ✅ DEBUG: Validate login data
# Enhanced input validation
if not login_data.email or not login_data.email.strip():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@@ -125,76 +125,88 @@ async def login(
detail="Password is required"
)
# Attempt login through AuthService
result = await AuthService.login_user(login_data, db)
# Login using enhanced service
result = await auth_service.login_user(login_data)
# Record successful login
if metrics:
metrics.increment_counter("login_success_total")
metrics.increment_counter("enhanced_login_success_total")
logger.info(f"Login successful for {login_data.email}")
return TokenResponse(**result)
logger.info("Login successful using repository pattern",
user_id=result.user.id,
email=login_data.email)
return result
except HTTPException as e:
# Record failed login with specific reason
if metrics:
reason = "validation_error" if e.status_code == 400 else "auth_failed"
metrics.increment_counter("login_failure_total", labels={"reason": reason})
metrics.increment_counter("enhanced_login_failure_total", labels={"reason": reason})
logger.warning(f"Login failed for {login_data.email}: {e.detail}")
logger.warning("Login failed using repository pattern",
email=login_data.email,
error=e.detail)
raise
except Exception as e:
# Record login system error
if metrics:
metrics.increment_counter("login_failure_total", labels={"reason": "error"})
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))
logger.error(f"Login system error for {login_data.email}: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Login failed"
)
@router.post("/refresh", response_model=TokenResponse)
@track_execution_time("token_refresh_duration_seconds", "auth-service")
@router.post("/refresh")
@track_execution_time("enhanced_token_refresh_duration_seconds", "auth-service")
async def refresh_token(
refresh_data: RefreshTokenRequest,
request: Request,
db: AsyncSession = Depends(get_db)
auth_service: EnhancedAuthService = Depends(get_auth_service)
):
"""Refresh access token"""
"""Refresh access token using repository pattern"""
metrics = get_metrics_collector(request)
try:
result = await AuthService.refresh_access_token(refresh_data.refresh_token, db)
result = await auth_service.refresh_access_token(refresh_data.refresh_token)
# Record successful refresh
if metrics:
metrics.increment_counter("token_refresh_success_total")
metrics.increment_counter("enhanced_token_refresh_success_total")
return TokenResponse(**result)
logger.debug("Access token refreshed using repository pattern")
return result
except HTTPException as e:
if metrics:
metrics.increment_counter("token_refresh_failure_total")
logger.warning(f"Token refresh failed: {e.detail}")
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("token_refresh_failure_total")
logger.error(f"Token refresh error: {e}")
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("token_verify_duration_seconds", "auth-service")
@track_execution_time("enhanced_token_verify_duration_seconds", "auth-service")
async def verify_token(
credentials: HTTPAuthorizationCredentials = Depends(security),
request: Request = None
request: Request = None,
auth_service: EnhancedAuthService = Depends(get_auth_service)
):
"""Verify access token and return user info"""
"""Verify access token using repository pattern"""
metrics = get_metrics_collector(request) if request else None
try:
@@ -204,74 +216,91 @@ async def verify_token(
detail="Authentication required"
)
result = await AuthService.verify_user_token(credentials.credentials)
result = await auth_service.verify_user_token(credentials.credentials)
# Record successful verification
if metrics:
metrics.increment_counter("token_verify_success_total")
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("token_verify_failure_total")
logger.warning(f"Token verification failed: {e.detail}")
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("token_verify_failure_total")
logger.error(f"Token verification error: {e}")
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("logout_duration_seconds", "auth-service")
@track_execution_time("enhanced_logout_duration_seconds", "auth-service")
async def logout(
refresh_data: RefreshTokenRequest,
request: Request,
db: AsyncSession = Depends(get_db)
credentials: HTTPAuthorizationCredentials = Depends(security),
auth_service: EnhancedAuthService = Depends(get_auth_service)
):
"""Logout user by revoking refresh token"""
"""Logout user using repository pattern"""
metrics = get_metrics_collector(request)
try:
success = await AuthService.logout(refresh_data.refresh_token, db)
# 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("logout_total", labels={"status": status_label})
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("logout_total", labels={"status": "error"})
logger.error(f"Logout error: {e}")
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"
)
# ================================================================
# PASSWORD MANAGEMENT ENDPOINTS
# ================================================================
@router.post("/change-password")
async def change_password(
password_data: PasswordChange,
credentials: HTTPAuthorizationCredentials = Depends(security),
request: Request = None,
db: AsyncSession = Depends(get_db)
auth_service: EnhancedAuthService = Depends(get_auth_service)
):
"""Change user password"""
"""Change user password using repository pattern"""
metrics = get_metrics_collector(request) if request else None
try:
@@ -282,7 +311,7 @@ async def change_password(
)
# Verify current token
payload = await AuthService.verify_user_token(credentials.credentials)
payload = await auth_service.verify_user_token(credentials.credentials)
user_id = payload.get("user_id")
if not user_id:
@@ -291,74 +320,194 @@ async def change_password(
detail="Invalid token"
)
# Validate new password
if not SecurityManager.validate_password(password_data.new_password):
# Validate new password length
if len(password_data.new_password) < 8:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="New password does not meet security requirements"
detail="New password must be at least 8 characters long"
)
# Change password logic would go here
# This is a simplified version - you'd need to implement the actual password change in AuthService
# Change password using enhanced service
success = await auth_service.change_password(
user_id,
password_data.current_password,
password_data.new_password
)
# Record password change
if metrics:
metrics.increment_counter("password_change_total", labels={"status": "success"})
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)
logger.info(f"Password changed for user: {user_id}")
return {"message": "Password changed successfully"}
except HTTPException:
raise
except Exception as e:
# Record password change error
if metrics:
metrics.increment_counter("password_change_total", labels={"status": "error"})
logger.error(f"Password change error: {e}")
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,
db: AsyncSession = Depends(get_db)
auth_service: EnhancedAuthService = Depends(get_auth_service)
):
"""Request password reset"""
"""Request password reset using repository pattern"""
metrics = get_metrics_collector(request)
try:
# Password reset logic would go here
# This is a simplified version - you'd need to implement email sending, etc.
# In a full implementation, you'd send an email with a reset token
# For now, just log the request
# Record password reset request
if metrics:
metrics.increment_counter("password_reset_total", labels={"status": "requested"})
metrics.increment_counter("enhanced_password_reset_total", labels={"status": "requested"})
logger.info("Password reset requested using repository pattern",
email=reset_data.email)
logger.info(f"Password reset requested for: {reset_data.email}")
return {"message": "Password reset email sent if account exists"}
except Exception as e:
# Record password reset error
if metrics:
metrics.increment_counter("password_reset_total", labels={"status": "error"})
logger.error(f"Password reset error: {e}")
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"
)
# ================================================================
# HEALTH AND STATUS ENDPOINTS
# ================================================================
@router.get("/health")
async def health_check():
"""Health check endpoint"""
"""Health check endpoint for enhanced auth service"""
return {
"status": "healthy",
"service": "auth-service",
"version": "1.0.0"
"service": "enhanced-auth-service",
"version": "2.0.0",
"features": ["repository-pattern", "dependency-injection", "enhanced-error-handling"]
}