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