REFACTOR API gateway fix 6
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user