# shared/auth/decorators.py - NEW FILE """ Authentication decorators for microservices """ from functools import wraps from fastapi import HTTPException, status, Request from typing import Callable, Optional def require_authentication(func: Callable) -> Callable: """Decorator to require authentication - assumes gateway has validated token""" @wraps(func) async def wrapper(*args, **kwargs): # Find request object in arguments request = None for arg in args: if isinstance(arg, Request): request = arg break if not request: # Check kwargs request = kwargs.get('request') if not request: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Request object not found" ) # Check if user context exists (set by gateway) if not hasattr(request.state, 'user') or not request.state.user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required" ) return await func(*args, **kwargs) return wrapper def require_tenant_access(func: Callable) -> Callable: """Decorator to require tenant access""" @wraps(func) async def wrapper(*args, **kwargs): # Find request object request = None for arg in args: if isinstance(arg, Request): request = arg break if not request or not hasattr(request.state, 'tenant_id'): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Tenant access required" ) return await func(*args, **kwargs) return wrapper def get_current_user(request: Request) -> dict: """Get current user from request state""" if not hasattr(request.state, 'user'): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not authenticated" ) return request.state.user def get_current_tenant_id(request: Request) -> Optional[str]: """Get current tenant ID from request state""" return getattr(request.state, 'tenant_id', None)