diff --git a/services/auth/app/api/auth.py b/services/auth/app/api/auth.py index 35a6ee76..997a8f22 100644 --- a/services/auth/app/api/auth.py +++ b/services/auth/app/api/auth.py @@ -96,7 +96,7 @@ async def register( metrics = get_metrics_collector(request) 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 if metrics: diff --git a/services/auth/app/core/security.py b/services/auth/app/core/security.py index 1735793f..fa5db5d3 100644 --- a/services/auth/app/core/security.py +++ b/services/auth/app/core/security.py @@ -1,10 +1,9 @@ -# services/auth/app/core/security.py +# services/auth/app/core/security.py - FIXED VERSION """ Security utilities for authentication service -FIXED VERSION - Consistent JWT token structure +FIXED VERSION - Consistent password hashing using passlib """ -import bcrypt import re import hashlib from datetime import datetime, timedelta, timezone @@ -12,12 +11,16 @@ from typing import Optional, Dict, Any import redis.asyncio as redis from fastapi import HTTPException, status import structlog +from passlib.context import CryptContext from app.core.config import settings from shared.auth.jwt_handler import JWTHandler 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 jwt_handler = JWTHandler(settings.JWT_SECRET_KEY, settings.JWT_ALGORITHM) @@ -29,14 +32,17 @@ class SecurityManager: @staticmethod def hash_password(password: str) -> str: - """Hash password using bcrypt""" - salt = bcrypt.gensalt(rounds=settings.BCRYPT_ROUNDS) - return bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8') + """Hash password using passlib bcrypt - FIXED""" + return pwd_context.hash(password) @staticmethod def verify_password(password: str, hashed_password: str) -> bool: - """Verify password against hash""" - return bcrypt.checkpw(password.encode('utf-8'), hashed_password.encode('utf-8')) + """Verify password against hash using passlib - FIXED""" + try: + return pwd_context.verify(password, hashed_password) + except Exception as e: + logger.error(f"Password verification error: {e}") + return False @staticmethod def validate_password(password: str) -> bool: @@ -59,49 +65,40 @@ class SecurityManager: return True @staticmethod - def create_access_token(user_data: Dict[str, Any]) -> str: - """ - Create JWT access token with CONSISTENT structure - FIXED: Uses shared JWT handler for consistency - """ - return jwt_handler.create_access_token( - user_data=user_data, - expires_delta=timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES) - ) + def create_access_token(user_data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str: + """Create JWT access token""" + if expires_delta: + expire = datetime.now(timezone.utc) + expires_delta + else: + expire = datetime.now(timezone.utc) + 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 def create_refresh_token(user_data: Dict[str, Any]) -> str: - """ - Create JWT refresh token with CONSISTENT structure - FIXED: Uses shared JWT handler for consistency - """ - return jwt_handler.create_refresh_token( - user_data=user_data, - expires_delta=timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS) - ) - - @staticmethod - def verify_token(token: str) -> Optional[Dict[str, Any]]: - """ - 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 + """Create JWT refresh token""" + expire = datetime.now(timezone.utc) + timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS) + + payload = { + "sub": user_data["user_id"], + "user_id": user_data["user_id"], + "type": "refresh", + "exp": expire, + "iat": datetime.now(timezone.utc) + } + + return jwt_handler.create_access_token(payload) @staticmethod async def track_login_attempt(email: str, ip_address: str, success: bool) -> None: diff --git a/services/auth/app/services/auth_service.py b/services/auth/app/services/auth_service.py index 7cc52cba..5a90f411 100644 --- a/services/auth/app/services/auth_service.py +++ b/services/auth/app/services/auth_service.py @@ -26,10 +26,7 @@ class AuthService: full_name: str, db: AsyncSession ) -> Dict[str, Any]: - """ - Register new user and return tokens directly (NEW METHOD) - Follows industry best practices for immediate authentication - """ + """Register new user and return tokens directly - COMPLETELY FIXED""" try: # Check if user already exists result = await db.execute(select(User).where(User.email == email)) @@ -48,48 +45,46 @@ class AuthService: hashed_password=hashed_password, full_name=full_name, is_active=True, - is_verified=False, # Will be verified via email + is_verified=False, created_at=datetime.now(timezone.utc) ) db.add(new_user) await db.flush() # Get user ID without committing - # Generate tokens immediately (shorter lifespan for unverified users) - access_token = SecurityManager.create_access_token( - user_data={ - "user_id": str(new_user.id), - "email": new_user.email, - "full_name": new_user.full_name, - "is_verified": new_user.is_verified - } - ) + # ✅ FIX 2: Create complete user_data for token generation + complete_user_data = { + "user_id": str(new_user.id), + "email": new_user.email, + "full_name": new_user.full_name, + "is_verified": new_user.is_verified + } - refresh_token_value = SecurityManager.create_refresh_token( - user_data={"user_id": str(new_user.id)} - ) + # Generate tokens with complete user data + 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 refresh_token = RefreshToken( user_id=new_user.id, 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 ) db.add(refresh_token) await db.commit() - # Publish registration event (async) + # Publish registration event try: - await publish_user_registered( - { + await publish_user_registered({ "user_id": str(new_user.id), "email": new_user.email, "full_name": new_user.full_name, "registered_at": new_user.created_at.isoformat() - } - ) + }) except Exception as e: logger.warning(f"Failed to publish registration event: {e}") diff --git a/services/data/requirements.txt b/services/data/requirements.txt index 51c8f8ca..16c08b15 100644 --- a/services/data/requirements.txt +++ b/services/data/requirements.txt @@ -37,7 +37,7 @@ python-json-logger==2.0.4 # Security python-jose[cryptography]==3.3.0 -bcrypt==4.1.2 +passlib[bcrypt]==1.7.4 # Testing pytest==7.4.3 diff --git a/test_new.sh b/test_new.sh index 0a9f40c3..a164af04 100755 --- a/test_new.sh +++ b/test_new.sh @@ -1,7 +1,7 @@ #!/bin/bash # Configuration API_BASE="http://localhost:8000" -EMAIL="test@bakery.com" +EMAIL="test4555@bakery.com" PASSWORD="TestPassword123!" # Colors for output