Initial microservices setup from artifacts
This commit is contained in:
0
services/auth/app/core/__init__.py
Normal file
0
services/auth/app/core/__init__.py
Normal file
47
services/auth/app/core/config.py
Normal file
47
services/auth/app/core/config.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
Authentication service configuration
|
||||
"""
|
||||
|
||||
import os
|
||||
from pydantic import BaseSettings
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Application settings"""
|
||||
|
||||
# Basic settings
|
||||
APP_NAME: str = "Authentication Service"
|
||||
VERSION: str = "1.0.0"
|
||||
DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
|
||||
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
|
||||
|
||||
# Database settings
|
||||
DATABASE_URL: str = os.getenv("DATABASE_URL", "postgresql+asyncpg://auth_user:auth_pass123@auth-db:5432/auth_db")
|
||||
|
||||
# Redis settings
|
||||
REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379/0")
|
||||
|
||||
# JWT settings
|
||||
JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "your-super-secret-jwt-key")
|
||||
JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256")
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("JWT_ACCESS_TOKEN_EXPIRE_MINUTES", "30"))
|
||||
JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = int(os.getenv("JWT_REFRESH_TOKEN_EXPIRE_DAYS", "7"))
|
||||
|
||||
# Password settings
|
||||
PASSWORD_MIN_LENGTH: int = 8
|
||||
PASSWORD_REQUIRE_UPPERCASE: bool = True
|
||||
PASSWORD_REQUIRE_LOWERCASE: bool = True
|
||||
PASSWORD_REQUIRE_NUMBERS: bool = True
|
||||
PASSWORD_REQUIRE_SYMBOLS: bool = False
|
||||
|
||||
# Security settings
|
||||
BCRYPT_ROUNDS: int = 12
|
||||
MAX_LOGIN_ATTEMPTS: int = 5
|
||||
LOCKOUT_DURATION_MINUTES: int = 30
|
||||
|
||||
# RabbitMQ settings
|
||||
RABBITMQ_URL: str = os.getenv("RABBITMQ_URL", "amqp://bakery:forecast123@rabbitmq:5672/")
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
settings = Settings()
|
||||
12
services/auth/app/core/database.py
Normal file
12
services/auth/app/core/database.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Database configuration for auth service
|
||||
"""
|
||||
|
||||
from shared.database.base import DatabaseManager
|
||||
from app.core.config import settings
|
||||
|
||||
# Initialize database manager
|
||||
database_manager = DatabaseManager(settings.DATABASE_URL)
|
||||
|
||||
# Alias for convenience
|
||||
get_db = database_manager.get_db
|
||||
153
services/auth/app/core/security.py
Normal file
153
services/auth/app/core/security.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""
|
||||
Security utilities for authentication service
|
||||
"""
|
||||
|
||||
import bcrypt
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any
|
||||
import redis.asyncio as redis
|
||||
from fastapi import HTTPException, status
|
||||
import logging
|
||||
|
||||
from app.core.config import settings
|
||||
from shared.auth.jwt_handler import JWTHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Initialize JWT handler
|
||||
jwt_handler = JWTHandler(settings.JWT_SECRET_KEY, settings.JWT_ALGORITHM)
|
||||
|
||||
# Redis client for session management
|
||||
redis_client = redis.from_url(settings.REDIS_URL)
|
||||
|
||||
class SecurityManager:
|
||||
"""Security utilities for authentication"""
|
||||
|
||||
@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')
|
||||
|
||||
@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'))
|
||||
|
||||
@staticmethod
|
||||
def validate_password(password: str) -> bool:
|
||||
"""Validate password strength"""
|
||||
if len(password) < settings.PASSWORD_MIN_LENGTH:
|
||||
return False
|
||||
|
||||
if settings.PASSWORD_REQUIRE_UPPERCASE and not re.search(r'[A-Z]', password):
|
||||
return False
|
||||
|
||||
if settings.PASSWORD_REQUIRE_LOWERCASE and not re.search(r'[a-z]', password):
|
||||
return False
|
||||
|
||||
if settings.PASSWORD_REQUIRE_NUMBERS and not re.search(r'\d', password):
|
||||
return False
|
||||
|
||||
if settings.PASSWORD_REQUIRE_SYMBOLS and not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def create_access_token(user_data: Dict[str, Any]) -> str:
|
||||
"""Create JWT access token"""
|
||||
expires_delta = timedelta(minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
return jwt_handler.create_access_token(user_data, expires_delta)
|
||||
|
||||
@staticmethod
|
||||
def create_refresh_token(user_data: Dict[str, Any]) -> str:
|
||||
"""Create JWT refresh token"""
|
||||
expires_delta = timedelta(days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS)
|
||||
return jwt_handler.create_refresh_token(user_data, expires_delta)
|
||||
|
||||
@staticmethod
|
||||
def verify_token(token: str) -> Optional[Dict[str, Any]]:
|
||||
"""Verify JWT token"""
|
||||
return jwt_handler.verify_token(token)
|
||||
|
||||
@staticmethod
|
||||
async def check_login_attempts(email: str) -> bool:
|
||||
"""Check if user has exceeded login attempts"""
|
||||
try:
|
||||
key = f"login_attempts:{email}"
|
||||
attempts = await redis_client.get(key)
|
||||
|
||||
if attempts is None:
|
||||
return True
|
||||
|
||||
return int(attempts) < settings.MAX_LOGIN_ATTEMPTS
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking login attempts: {e}")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
async def increment_login_attempts(email: str):
|
||||
"""Increment login attempts counter"""
|
||||
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)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error incrementing login attempts: {e}")
|
||||
|
||||
@staticmethod
|
||||
async def clear_login_attempts(email: str):
|
||||
"""Clear login attempts counter"""
|
||||
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):
|
||||
"""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)
|
||||
|
||||
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"""
|
||||
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
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error verifying refresh token: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
async def revoke_refresh_token(user_id: str):
|
||||
"""Revoke refresh token"""
|
||||
try:
|
||||
key = f"refresh_token:{user_id}"
|
||||
await redis_client.delete(key)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error revoking refresh token: {e}")
|
||||
|
||||
# Global security manager instance
|
||||
security_manager = SecurityManager()
|
||||
Reference in New Issue
Block a user