Adds auth module

This commit is contained in:
Urtzi Alfaro
2025-07-17 21:25:27 +02:00
parent 654d1c2fe8
commit efca9a125a
19 changed files with 1169 additions and 406 deletions

View File

@@ -1,9 +1,13 @@
# ================================================================
# services/auth/app/core/security.py (COMPLETE VERSION)
# ================================================================
"""
Security utilities for authentication service
"""
import bcrypt
import re
import hashlib
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
import redis.asyncio as redis
@@ -72,6 +76,11 @@ class SecurityManager:
"""Verify JWT token"""
return jwt_handler.verify_token(token)
@staticmethod
def hash_token(token: str) -> str:
"""Hash token for storage"""
return hashlib.sha256(token.encode()).hexdigest()
@staticmethod
async def check_login_attempts(email: str) -> bool:
"""Check if user has exceeded login attempts"""
@@ -83,71 +92,64 @@ class SecurityManager:
return True
return int(attempts) < settings.MAX_LOGIN_ATTEMPTS
except Exception as e:
logger.error(f"Error checking login attempts: {e}")
return True
return True # Allow on error
@staticmethod
async def increment_login_attempts(email: str):
"""Increment login attempts counter"""
async def increment_login_attempts(email: str) -> None:
"""Increment login attempts for email"""
try:
key = f"login_attempts:{email}"
current_attempts = await redis_client.incr(key)
# Set TTL on first attempt
if current_attempts == 1:
await redis_client.expire(key, settings.LOCKOUT_DURATION_MINUTES * 60)
await redis_client.incr(key)
await redis_client.expire(key, settings.LOCKOUT_DURATION_MINUTES * 60)
except Exception as e:
logger.error(f"Error incrementing login attempts: {e}")
@staticmethod
async def clear_login_attempts(email: str):
"""Clear login attempts counter"""
async def clear_login_attempts(email: str) -> None:
"""Clear login attempts for email"""
try:
key = f"login_attempts:{email}"
await redis_client.delete(key)
except Exception as e:
logger.error(f"Error clearing login attempts: {e}")
@staticmethod
async def store_refresh_token(user_id: str, refresh_token: str):
async def store_refresh_token(user_id: str, token: str) -> None:
"""Store refresh token in Redis"""
try:
key = f"refresh_token:{user_id}"
expires_seconds = settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS * 24 * 3600
await redis_client.setex(key, expires_seconds, refresh_token)
token_hash = SecurityManager.hash_token(token)
key = f"refresh_token:{user_id}:{token_hash}"
# Store for the duration of the refresh token
expire_seconds = settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS * 24 * 60 * 60
await redis_client.setex(key, expire_seconds, "valid")
except Exception as e:
logger.error(f"Error storing refresh token: {e}")
@staticmethod
async def verify_refresh_token(user_id: str, refresh_token: str) -> bool:
"""Verify refresh token"""
async def verify_refresh_token(user_id: str, token: str) -> bool:
"""Verify refresh token exists in Redis"""
try:
key = f"refresh_token:{user_id}"
stored_token = await redis_client.get(key)
if stored_token is None:
return False
return stored_token.decode() == refresh_token
token_hash = SecurityManager.hash_token(token)
key = f"refresh_token:{user_id}:{token_hash}"
result = await redis_client.get(key)
return result is not None
except Exception as e:
logger.error(f"Error verifying refresh token: {e}")
return False
@staticmethod
async def revoke_refresh_token(user_id: str):
async def revoke_refresh_token(user_id: str, token: str) -> None:
"""Revoke refresh token"""
try:
key = f"refresh_token:{user_id}"
token_hash = SecurityManager.hash_token(token)
key = f"refresh_token:{user_id}:{token_hash}"
await redis_client.delete(key)
except Exception as e:
logger.error(f"Error revoking refresh token: {e}")
# Global security manager instance
security_manager = SecurityManager()
# Create singleton instance
security_manager = SecurityManager()