import time import structlog from typing import Dict, Any from shared.auth.jwt_handler import JWTHandler from app.core.config import settings logger = structlog.get_logger() class ServiceAuthenticator: """Handles service-to-service authentication via gateway""" def __init__(self): self.jwt_handler = JWTHandler(settings.JWT_SECRET_KEY) self._cached_token = None self._token_expires_at = 0 async def get_service_token(self) -> str: """ Get a valid service token, using cache when possible Creates JWT tokens that the gateway will accept """ current_time = int(time.time()) # Return cached token if still valid (with 5 min buffer) if (self._cached_token and self._token_expires_at > current_time + 300): return self._cached_token # Create new service token token_expires_at = current_time + 3600 # 1 hour service_payload = { # ✅ Required fields for gateway middleware "sub": "training-service", "user_id": "training-service", "email": "training-service@internal", "type": "access", # ✅ Must be "access" for gateway # ✅ Expiration and timing "exp": token_expires_at, "iat": current_time, "iss": "training-service", # ✅ Service identification "service": "training", "full_name": "Training Service", "is_verified": True, "is_active": True, # ✅ Optional tenant context (can be overridden per request) "tenant_id": None } try: token = self.jwt_handler.create_access_token_from_payload(service_payload) # Cache the token self._cached_token = token self._token_expires_at = token_expires_at logger.debug("Created new service token", expires_at=token_expires_at) return token except Exception as e: logger.error(f"Failed to create service token: {e}") raise ValueError(f"Service token creation failed: {e}") def get_request_headers(self, tenant_id: str = None) -> Dict[str, str]: """Get standard headers for service requests""" headers = { "X-Service": "training-service", "User-Agent": "training-service/1.0.0" } if tenant_id: headers["X-Tenant-ID"] = str(tenant_id) return headers # Global authenticator instance service_auth = ServiceAuthenticator()