Fix auth service login failure by correcting logging calls
This commit is contained in:
@@ -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"
|
||||||
@@ -637,6 +688,24 @@ class EnhancedAuthService:
|
|||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
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
|
||||||
|
|||||||
144
services/auth/app/utils/subscription_fetcher.py
Normal file
144
services/auth/app/utils/subscription_fetcher.py
Normal 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)}"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user