REFACTOR API gateway fix 6

This commit is contained in:
Urtzi Alfaro
2025-07-26 21:48:53 +02:00
parent 7d5c8bc9a4
commit de3bd5e541
5 changed files with 66 additions and 74 deletions

View File

@@ -96,7 +96,7 @@ async def register(
metrics = get_metrics_collector(request) metrics = get_metrics_collector(request)
try: try:
result = await AuthService.register_user_with_tokens(user_data.email, user_data.email, user_data.full_name, db) result = await AuthService.register_user_with_tokens(user_data.email, user_data.password, user_data.full_name, db)
# Record successful registration # Record successful registration
if metrics: if metrics:

View File

@@ -1,10 +1,9 @@
# services/auth/app/core/security.py # services/auth/app/core/security.py - FIXED VERSION
""" """
Security utilities for authentication service Security utilities for authentication service
FIXED VERSION - Consistent JWT token structure FIXED VERSION - Consistent password hashing using passlib
""" """
import bcrypt
import re import re
import hashlib import hashlib
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
@@ -12,12 +11,16 @@ from typing import Optional, Dict, Any
import redis.asyncio as redis import redis.asyncio as redis
from fastapi import HTTPException, status from fastapi import HTTPException, status
import structlog import structlog
from passlib.context import CryptContext
from app.core.config import settings from app.core.config import settings
from shared.auth.jwt_handler import JWTHandler from shared.auth.jwt_handler import JWTHandler
logger = structlog.get_logger() logger = structlog.get_logger()
# ✅ FIX: Use passlib for consistent password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# Initialize JWT handler with SAME configuration as gateway # Initialize JWT handler with SAME configuration as gateway
jwt_handler = JWTHandler(settings.JWT_SECRET_KEY, settings.JWT_ALGORITHM) jwt_handler = JWTHandler(settings.JWT_SECRET_KEY, settings.JWT_ALGORITHM)
@@ -29,14 +32,17 @@ class SecurityManager:
@staticmethod @staticmethod
def hash_password(password: str) -> str: def hash_password(password: str) -> str:
"""Hash password using bcrypt""" """Hash password using passlib bcrypt - FIXED"""
salt = bcrypt.gensalt(rounds=settings.BCRYPT_ROUNDS) return pwd_context.hash(password)
return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')
@staticmethod @staticmethod
def verify_password(password: str, hashed_password: str) -> bool: def verify_password(password: str, hashed_password: str) -> bool:
"""Verify password against hash""" """Verify password against hash using passlib - FIXED"""
return bcrypt.checkpw(password.encode('utf-8'), hashed_password.encode('utf-8')) try:
return pwd_context.verify(password, hashed_password)
except Exception as e:
logger.error(f"Password verification error: {e}")
return False
@staticmethod @staticmethod
def validate_password(password: str) -> bool: def validate_password(password: str) -> bool:
@@ -59,49 +65,40 @@ class SecurityManager:
return True return True
@staticmethod @staticmethod
def create_access_token(user_data: Dict[str, Any]) -> str: def create_access_token(user_data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str:
""" """Create JWT access token"""
Create JWT access token with CONSISTENT structure if expires_delta:
FIXED: Uses shared JWT handler for consistency expire = datetime.now(timezone.utc) + expires_delta
""" else:
return jwt_handler.create_access_token( expire = datetime.now(timezone.utc) + timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES)
user_data=user_data,
expires_delta=timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES) payload = {
) "sub": user_data["user_id"],
"user_id": user_data["user_id"],
"email": user_data["email"],
"type": "access",
"full_name": user_data.get("full_name", ""),
"is_verified": user_data.get("is_verified", False),
"exp": expire,
"iat": datetime.now(timezone.utc)
}
return jwt_handler.create_access_token(payload)
@staticmethod @staticmethod
def create_refresh_token(user_data: Dict[str, Any]) -> str: def create_refresh_token(user_data: Dict[str, Any]) -> str:
""" """Create JWT refresh token"""
Create JWT refresh token with CONSISTENT structure expire = datetime.now(timezone.utc) + timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS)
FIXED: Uses shared JWT handler for consistency
""" payload = {
return jwt_handler.create_refresh_token( "sub": user_data["user_id"],
user_data=user_data, "user_id": user_data["user_id"],
expires_delta=timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS) "type": "refresh",
) "exp": expire,
"iat": datetime.now(timezone.utc)
@staticmethod }
def verify_token(token: str) -> Optional[Dict[str, Any]]:
""" return jwt_handler.create_access_token(payload)
Verify JWT token with CONSISTENT validation
FIXED: Uses shared JWT handler for consistency
"""
try:
return jwt_handler.verify_token(token)
except Exception as e:
logger.warning(f"Token verification failed: {e}")
return None
@staticmethod
def decode_token(token: str) -> Optional[Dict[str, Any]]:
"""
Decode JWT token without verification (for debugging)
"""
try:
return jwt_handler.decode_token_unsafe(token)
except Exception as e:
logger.warning(f"Token decode failed: {e}")
return None
@staticmethod @staticmethod
async def track_login_attempt(email: str, ip_address: str, success: bool) -> None: async def track_login_attempt(email: str, ip_address: str, success: bool) -> None:

View File

@@ -26,10 +26,7 @@ class AuthService:
full_name: str, full_name: str,
db: AsyncSession db: AsyncSession
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """Register new user and return tokens directly - COMPLETELY FIXED"""
Register new user and return tokens directly (NEW METHOD)
Follows industry best practices for immediate authentication
"""
try: try:
# Check if user already exists # Check if user already exists
result = await db.execute(select(User).where(User.email == email)) result = await db.execute(select(User).where(User.email == email))
@@ -48,48 +45,46 @@ class AuthService:
hashed_password=hashed_password, hashed_password=hashed_password,
full_name=full_name, full_name=full_name,
is_active=True, is_active=True,
is_verified=False, # Will be verified via email is_verified=False,
created_at=datetime.now(timezone.utc) created_at=datetime.now(timezone.utc)
) )
db.add(new_user) db.add(new_user)
await db.flush() # Get user ID without committing await db.flush() # Get user ID without committing
# Generate tokens immediately (shorter lifespan for unverified users) # ✅ FIX 2: Create complete user_data for token generation
access_token = SecurityManager.create_access_token( complete_user_data = {
user_data={ "user_id": str(new_user.id),
"user_id": str(new_user.id), "email": new_user.email,
"email": new_user.email, "full_name": new_user.full_name,
"full_name": new_user.full_name, "is_verified": new_user.is_verified
"is_verified": new_user.is_verified }
}
)
refresh_token_value = SecurityManager.create_refresh_token( # Generate tokens with complete user data
user_data={"user_id": str(new_user.id)} access_token = SecurityManager.create_access_token(user_data=complete_user_data)
)
# ✅ FIX 3: Pass complete user data for refresh token too
refresh_token_value = SecurityManager.create_refresh_token(user_data=complete_user_data)
# Store refresh token in database # Store refresh token in database
refresh_token = RefreshToken( refresh_token = RefreshToken(
user_id=new_user.id, user_id=new_user.id,
token=refresh_token_value, token=refresh_token_value,
expires_at=datetime.now(timezone.utc) + timedelta(days=7), # Shorter for new users expires_at=datetime.now(timezone.utc) + timedelta(days=7),
is_revoked=False is_revoked=False
) )
db.add(refresh_token) db.add(refresh_token)
await db.commit() await db.commit()
# Publish registration event (async) # Publish registration event
try: try:
await publish_user_registered( await publish_user_registered({
{
"user_id": str(new_user.id), "user_id": str(new_user.id),
"email": new_user.email, "email": new_user.email,
"full_name": new_user.full_name, "full_name": new_user.full_name,
"registered_at": new_user.created_at.isoformat() "registered_at": new_user.created_at.isoformat()
} })
)
except Exception as e: except Exception as e:
logger.warning(f"Failed to publish registration event: {e}") logger.warning(f"Failed to publish registration event: {e}")

View File

@@ -37,7 +37,7 @@ python-json-logger==2.0.4
# Security # Security
python-jose[cryptography]==3.3.0 python-jose[cryptography]==3.3.0
bcrypt==4.1.2 passlib[bcrypt]==1.7.4
# Testing # Testing
pytest==7.4.3 pytest==7.4.3

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# Configuration # Configuration
API_BASE="http://localhost:8000" API_BASE="http://localhost:8000"
EMAIL="test@bakery.com" EMAIL="test4555@bakery.com"
PASSWORD="TestPassword123!" PASSWORD="TestPassword123!"
# Colors for output # Colors for output