REFACTOR API gateway fix 3
This commit is contained in:
@@ -1,36 +1,89 @@
|
||||
# services/auth/app/api/auth.py - UPDATED TO RETURN TOKENS FROM REGISTRATION
|
||||
# services/auth/app/api/auth.py - Fixed Login Method
|
||||
"""
|
||||
Authentication API routes - Updated to return tokens directly from registration
|
||||
Following industry best practices with unified token response format
|
||||
Authentication API endpoints - FIXED VERSION
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import Optional
|
||||
import structlog
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.schemas.auth import (
|
||||
UserRegistration, UserLogin, TokenResponse,
|
||||
RefreshTokenRequest, UserResponse, PasswordChange,
|
||||
PasswordReset, TokenVerification
|
||||
)
|
||||
from app.services.auth_service import AuthService
|
||||
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 shared.monitoring.decorators import track_execution_time
|
||||
from shared.monitoring.metrics import get_metrics_collector
|
||||
|
||||
logger = structlog.get_logger()
|
||||
router = APIRouter(tags=["authentication"])
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
def get_metrics_collector(request: Request):
|
||||
"""Get metrics collector from app state"""
|
||||
return getattr(request.app.state, 'metrics_collector', None)
|
||||
router = APIRouter()
|
||||
security = HTTPBearer()
|
||||
|
||||
# ================================================================
|
||||
# AUTHENTICATION ENDPOINTS
|
||||
# ================================================================
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
@track_execution_time("login_duration_seconds", "auth-service")
|
||||
async def login(
|
||||
login_data: UserLogin,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Login user and return tokens
|
||||
FIXED: Proper error handling and login attempt tracking
|
||||
"""
|
||||
metrics = get_metrics_collector(request)
|
||||
|
||||
try:
|
||||
# Check if account is locked due to too many failed attempts
|
||||
can_attempt = await SecurityManager.check_login_attempts(login_data.email)
|
||||
if not can_attempt:
|
||||
if metrics:
|
||||
metrics.increment_counter("login_failure_total", labels={"reason": "rate_limited"})
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
detail=f"Too many login attempts. Please try again in {SecurityManager.settings.LOCKOUT_DURATION_MINUTES} minutes."
|
||||
)
|
||||
|
||||
# Attempt login through AuthService
|
||||
result = await AuthService.login(login_data.email, login_data.password, db)
|
||||
|
||||
# Clear login attempts on successful login
|
||||
await SecurityManager.clear_login_attempts(login_data.email)
|
||||
|
||||
# Record successful login
|
||||
if metrics:
|
||||
metrics.increment_counter("login_success_total")
|
||||
|
||||
logger.info(f"Login successful for {login_data.email}")
|
||||
return TokenResponse(**result)
|
||||
|
||||
except HTTPException as e:
|
||||
# Don't increment attempts for rate limiting errors (already handled above)
|
||||
if e.status_code != status.HTTP_429_TOO_MANY_REQUESTS:
|
||||
# Increment login attempts on authentication failure
|
||||
await SecurityManager.increment_login_attempts(login_data.email)
|
||||
|
||||
# Record failed login
|
||||
if metrics:
|
||||
reason = "rate_limited" if e.status_code == 429 else "auth_failed"
|
||||
metrics.increment_counter("login_failure_total", labels={"reason": reason})
|
||||
|
||||
logger.warning(f"Login failed for {login_data.email}: {e.detail}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
# Increment login attempts on any other error
|
||||
await SecurityManager.increment_login_attempts(login_data.email)
|
||||
|
||||
# Record login error
|
||||
if metrics:
|
||||
metrics.increment_counter("login_failure_total", labels={"reason": "error"})
|
||||
|
||||
logger.error(f"Login error for {login_data.email}: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Login failed"
|
||||
)
|
||||
|
||||
@router.post("/register", response_model=TokenResponse)
|
||||
@track_execution_time("registration_duration_seconds", "auth-service")
|
||||
@@ -39,31 +92,17 @@ async def register(
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Register a new user and return tokens directly"""
|
||||
"""Register new user"""
|
||||
metrics = get_metrics_collector(request)
|
||||
|
||||
try:
|
||||
# Validate password strength
|
||||
if not SecurityManager.validate_password(user_data.password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Password does not meet security requirements"
|
||||
)
|
||||
|
||||
# Create user and generate tokens in one operation
|
||||
result = await AuthService.register_user_with_tokens(
|
||||
email=user_data.email,
|
||||
password=user_data.password,
|
||||
full_name=user_data.full_name,
|
||||
db=db
|
||||
)
|
||||
result = await AuthService.register(user_data, db)
|
||||
|
||||
# Record successful registration
|
||||
if metrics:
|
||||
metrics.increment_counter("registration_total", labels={"status": "success"})
|
||||
|
||||
logger.info(f"User registration with tokens successful: {user_data.email}")
|
||||
|
||||
logger.info(f"User registered successfully: {user_data.email}")
|
||||
return TokenResponse(**result)
|
||||
|
||||
except HTTPException as e:
|
||||
@@ -80,58 +119,6 @@ async def register(
|
||||
detail="Registration failed"
|
||||
)
|
||||
|
||||
@router.post("/login", response_model=TokenResponse)
|
||||
@track_execution_time("login_duration_seconds", "auth-service")
|
||||
async def login(
|
||||
login_data: UserLogin,
|
||||
request: Request,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Login user and return tokens"""
|
||||
metrics = get_metrics_collector(request)
|
||||
|
||||
try:
|
||||
# Check login attempts (rate limiting)
|
||||
#attempts = await SecurityManager.get_login_attempts(login_data.email)
|
||||
#if attempts >= 5:
|
||||
# raise HTTPException(
|
||||
# status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
||||
# detail="Too many login attempts. Please try again later."
|
||||
# )
|
||||
|
||||
# Attempt login
|
||||
result = await AuthService.login(login_data.email, login_data.password, db)
|
||||
|
||||
# Clear login attempts on success
|
||||
await SecurityManager.clear_login_attempts(login_data.email)
|
||||
|
||||
# Record successful login
|
||||
if metrics:
|
||||
metrics.increment_counter("login_success_total")
|
||||
|
||||
logger.info(f"Login successful for {login_data.email}")
|
||||
return TokenResponse(**result)
|
||||
|
||||
except HTTPException as e:
|
||||
# Increment login attempts on failure
|
||||
await SecurityManager.increment_login_attempts(login_data.email)
|
||||
|
||||
# Record failed login
|
||||
if metrics:
|
||||
metrics.increment_counter("login_failure_total", labels={"reason": "auth_failed"})
|
||||
logger.warning(f"Login failed for {login_data.email}: {e.detail}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
# Record login error
|
||||
if metrics:
|
||||
metrics.increment_counter("login_failure_total", labels={"reason": "error"})
|
||||
logger.error(f"Login error for {login_data.email}: {e}")
|
||||
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")
|
||||
async def refresh_token(
|
||||
@@ -149,7 +136,6 @@ async def refresh_token(
|
||||
if metrics:
|
||||
metrics.increment_counter("token_refresh_success_total")
|
||||
|
||||
logger.info("Token refresh successful")
|
||||
return TokenResponse(**result)
|
||||
|
||||
except HTTPException as e:
|
||||
@@ -157,7 +143,6 @@ async def refresh_token(
|
||||
metrics.increment_counter("token_refresh_failure_total")
|
||||
logger.warning(f"Token refresh failed: {e.detail}")
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
if metrics:
|
||||
metrics.increment_counter("token_refresh_failure_total")
|
||||
@@ -167,37 +152,40 @@ async def refresh_token(
|
||||
detail="Token refresh failed"
|
||||
)
|
||||
|
||||
@router.post("/verify", response_model=TokenVerification)
|
||||
@track_execution_time("token_verification_duration_seconds", "auth-service")
|
||||
@router.post("/verify")
|
||||
@track_execution_time("token_verify_duration_seconds", "auth-service")
|
||||
async def verify_token(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
||||
request: Request = None
|
||||
):
|
||||
"""Verify access token"""
|
||||
"""Verify access token and return user info"""
|
||||
metrics = get_metrics_collector(request) if request else None
|
||||
|
||||
if not credentials:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authorization header required"
|
||||
)
|
||||
|
||||
try:
|
||||
if not credentials:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication required"
|
||||
)
|
||||
|
||||
result = await AuthService.verify_user_token(credentials.credentials)
|
||||
|
||||
# Record successful verification
|
||||
if metrics:
|
||||
metrics.increment_counter("token_verify_success_total")
|
||||
|
||||
return TokenVerification(
|
||||
valid=True,
|
||||
user_id=result.get("user_id"),
|
||||
email=result.get("email"),
|
||||
exp=result.get("exp")
|
||||
)
|
||||
return {
|
||||
"valid": True,
|
||||
"user_id": result.get("user_id"),
|
||||
"email": result.get("email"),
|
||||
"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}")
|
||||
raise
|
||||
except Exception as e:
|
||||
if metrics:
|
||||
|
||||
Reference in New Issue
Block a user