Fix auth service login failure by correcting logging calls

This commit is contained in:
Urtzi Alfaro
2026-01-10 21:43:31 +01:00
parent b089c216db
commit cc53037552
2 changed files with 220 additions and 7 deletions

View File

@@ -15,6 +15,7 @@ from app.schemas.auth import UserRegistration, UserLogin, TokenResponse, UserRes
from app.models.users import User from app.models.users import User
from app.models.tokens import RefreshToken from app.models.tokens import RefreshToken
from app.core.security import SecurityManager from app.core.security import SecurityManager
from app.utils.subscription_fetcher import SubscriptionFetcher
from shared.messaging import UnifiedEventPublisher, EVENT_TYPES from shared.messaging import UnifiedEventPublisher, EVENT_TYPES
from shared.database.unit_of_work import UnitOfWork from shared.database.unit_of_work import UnitOfWork
from shared.database.transactions import transactional from shared.database.transactions import transactional
@@ -225,7 +226,7 @@ class EnhancedAuthService:
} }
) )
except Exception as e: except Exception as e:
logger.warning("Failed to publish registration event", error=str(e)) logger.warning("Failed to publish registration event: %s", str(e))
logger.info("User registered successfully using repository pattern", logger.info("User registered successfully using repository pattern",
user_id=new_user.id, user_id=new_user.id,
@@ -288,7 +289,28 @@ class EnhancedAuthService:
logger.debug("Existing tokens revoked using repository pattern", logger.debug("Existing tokens revoked using repository pattern",
user_id=user.id) user_id=user.id)
# NEW: Fetch subscription data for JWT enrichment
# This happens ONCE at login, not per-request
from app.core.config import settings
subscription_fetcher = SubscriptionFetcher(
tenant_service_url=settings.TENANT_SERVICE_URL # Now properly configurable
)
# Get service token for inter-service communication
service_token = await self._get_service_token()
subscription_context = await subscription_fetcher.get_user_subscription_context(
user_id=str(user.id),
service_token=service_token
)
logger.debug("Fetched subscription context for JWT enrichment",
user_id=user.id,
subscription_tier=subscription_context.get("subscription", {}).get("tier"))
# Create tokens with different payloads # Create tokens with different payloads
subscription_data = subscription_context.get("subscription") or {}
access_token_data = { access_token_data = {
"user_id": str(user.id), "user_id": str(user.id),
"email": user.email, "email": user.email,
@@ -296,7 +318,14 @@ class EnhancedAuthService:
"is_verified": user.is_verified, "is_verified": user.is_verified,
"is_active": user.is_active, "is_active": user.is_active,
"role": user.role, "role": user.role,
"type": "access" "type": "access",
# NEW: Add subscription data to JWT payload
"tenant_id": subscription_context.get("tenant_id"),
"tenant_role": subscription_context.get("tenant_role"),
"subscription": subscription_data,
"subscription_tier": subscription_data.get("tier", "starter"), # Add direct field for gateway
"subscription_from_jwt": True, # Flag for gateway to use JWT data
"tenant_access": subscription_context.get("tenant_access")
} }
refresh_token_data = { refresh_token_data = {
@@ -339,7 +368,7 @@ class EnhancedAuthService:
} }
) )
except Exception as e: except Exception as e:
logger.warning("Failed to publish login event", error=str(e)) logger.warning("Failed to publish login event: %s", str(e))
logger.info("User logged in successfully using repository pattern", logger.info("User logged in successfully using repository pattern",
user_id=user.id, user_id=user.id,
@@ -425,7 +454,24 @@ class EnhancedAuthService:
detail="User not found or inactive" detail="User not found or inactive"
) )
# Create new access token # NEW: Fetch FRESH subscription data for token refresh
# This ensures subscription changes propagate within token expiry period
subscription_fetcher = SubscriptionFetcher(
tenant_service_url=settings.TENANT_SERVICE_URL # Now properly configurable
)
service_token = await self._get_service_token()
subscription_context = await subscription_fetcher.get_user_subscription_context(
user_id=str(user.id),
service_token=service_token
)
logger.debug("Fetched fresh subscription context for token refresh",
user_id=user.id,
subscription_tier=subscription_context.get("subscription", {}).get("tier"))
# Create new access token with updated subscription data
access_token_data = { access_token_data = {
"user_id": str(user.id), "user_id": str(user.id),
"email": user.email, "email": user.email,
@@ -433,7 +479,12 @@ class EnhancedAuthService:
"is_verified": user.is_verified, "is_verified": user.is_verified,
"is_active": user.is_active, "is_active": user.is_active,
"role": user.role, "role": user.role,
"type": "access" "type": "access",
# NEW: Add fresh subscription data to JWT payload
"tenant_id": subscription_context.get("tenant_id"),
"tenant_role": subscription_context.get("tenant_role"),
"subscription": subscription_context.get("subscription"),
"tenant_access": subscription_context.get("tenant_access")
} }
new_access_token = SecurityManager.create_access_token(user_data=access_token_data) new_access_token = SecurityManager.create_access_token(user_data=access_token_data)
@@ -450,7 +501,7 @@ class EnhancedAuthService:
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error("Token refresh failed using repository pattern", error=str(e)) logger.error("Token refresh failed using repository pattern: %s", str(e))
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token refresh failed" detail="Token refresh failed"
@@ -469,7 +520,7 @@ class EnhancedAuthService:
return payload return payload
except Exception as e: except Exception as e:
logger.error("Token verification error using repository pattern", error=str(e)) logger.error("Token verification error using repository pattern: %s", str(e))
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token" detail="Invalid token"
@@ -638,6 +689,24 @@ class EnhancedAuthService:
error=str(e)) error=str(e))
return False return False
async def _get_service_token(self) -> str:
"""
Get service token for inter-service communication.
This is used to fetch subscription data from tenant service.
"""
try:
# Create a proper service token with JWT using SecurityManager
service_token = SecurityManager.create_service_token("auth-service")
logger.debug("Generated service token for tenant service communication")
return service_token
except Exception as e:
logger.error(f"Failed to get service token: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to authenticate with tenant service"
)
# Legacy compatibility - alias EnhancedAuthService as AuthService # Legacy compatibility - alias EnhancedAuthService as AuthService
AuthService = EnhancedAuthService AuthService = EnhancedAuthService

View File

@@ -0,0 +1,144 @@
"""Fetches subscription data for JWT enrichment at login time"""
from typing import Dict, Any, Optional
import httpx
import logging
from fastapi import HTTPException, status
logger = logging.getLogger(__name__)
class SubscriptionFetcher:
def __init__(self, tenant_service_url: str):
self.tenant_service_url = tenant_service_url.rstrip('/')
logger.info("SubscriptionFetcher initialized with URL: %s", self.tenant_service_url)
async def get_user_subscription_context(
self,
user_id: str,
service_token: str
) -> Dict[str, Any]:
"""
Fetch user's tenant memberships and subscription data.
Called ONCE at login, not per-request.
Returns:
{
"tenant_id": "primary-tenant-uuid",
"tenant_role": "owner",
"subscription": {
"tier": "professional",
"status": "active",
"valid_until": "2025-02-15T00:00:00Z"
},
"tenant_access": [
{"id": "uuid", "role": "admin", "tier": "starter"}
]
}
"""
try:
logger.debug("Fetching subscription data for user: %s", user_id)
async with httpx.AsyncClient(timeout=10.0) as client:
# Get user's tenant memberships - corrected URL
memberships_url = f"{self.tenant_service_url}/api/v1/tenants/members/user/{user_id}"
headers = {
"Authorization": f"Bearer {service_token}",
"Content-Type": "application/json"
}
logger.debug("Fetching user memberships from URL: %s", memberships_url)
response = await client.get(memberships_url, headers=headers)
if response.status_code != 200:
logger.error(f"Failed to fetch user memberships: {response.status_code}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to fetch user memberships"
)
memberships = response.json()
if not memberships:
logger.info(f"User {user_id} has no tenant memberships - returning default subscription context")
return {
"tenant_id": None,
"tenant_role": None,
"subscription": {
"tier": "starter",
"status": "active",
"valid_until": None
},
"tenant_access": []
}
# Get primary tenant (first one, or the one with highest role)
primary_membership = memberships[0]
for membership in memberships:
if membership.get("role") == "owner":
primary_membership = membership
break
primary_tenant_id = primary_membership["tenant_id"]
primary_role = primary_membership["role"]
# Get subscription for primary tenant - FIXED: Use correct endpoint
subscription_url = f"{self.tenant_service_url}/api/v1/tenants/subscriptions/{primary_tenant_id}/active"
subscription_response = await client.get(subscription_url, headers=headers)
if subscription_response.status_code != 200:
logger.error(f"Failed to fetch subscription for tenant {primary_tenant_id}: {subscription_response.status_code}")
# Return with basic info but no subscription
return {
"tenant_id": primary_tenant_id,
"tenant_role": primary_role,
"subscription": None,
"tenant_access": memberships
}
subscription_data = subscription_response.json()
# Build tenant access list with subscription info
tenant_access = []
for membership in memberships:
tenant_id = membership["tenant_id"]
role = membership["role"]
# Get subscription for each tenant - FIXED: Use correct endpoint
tenant_sub_url = f"{self.tenant_service_url}/api/v1/tenants/subscriptions/{tenant_id}/active"
tenant_sub_response = await client.get(tenant_sub_url, headers=headers)
tier = "starter" # default
if tenant_sub_response.status_code == 200:
tenant_sub = tenant_sub_response.json()
tier = tenant_sub.get("plan", "starter")
tenant_access.append({
"id": tenant_id,
"role": role,
"tier": tier
})
return {
"tenant_id": primary_tenant_id,
"tenant_role": primary_role,
"subscription": {
"tier": subscription_data.get("plan", "starter"),
"status": subscription_data.get("status", "active"),
"valid_until": subscription_data.get("valid_until", None)
},
"tenant_access": tenant_access
}
except httpx.HTTPError as e:
logger.error(f"HTTP error fetching subscription data: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"HTTP error fetching subscription data: {str(e)}"
)
except Exception as e:
logger.error(f"Error fetching subscription data: {str(e)}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Error fetching subscription data: {str(e)}"
)