264 lines
9.3 KiB
Python
264 lines
9.3 KiB
Python
|
|
# shared/clients/auth_client.py
|
||
|
|
"""
|
||
|
|
Auth Service Client for Inter-Service Communication
|
||
|
|
Provides methods to interact with the authentication/onboarding service
|
||
|
|
"""
|
||
|
|
|
||
|
|
from typing import Optional, Dict, Any, List
|
||
|
|
import structlog
|
||
|
|
|
||
|
|
from shared.clients.base_service_client import BaseServiceClient
|
||
|
|
from shared.config.base import BaseServiceSettings
|
||
|
|
|
||
|
|
logger = structlog.get_logger()
|
||
|
|
|
||
|
|
|
||
|
|
class AuthServiceClient(BaseServiceClient):
|
||
|
|
"""Client for interacting with the Auth Service"""
|
||
|
|
|
||
|
|
def __init__(self, config: BaseServiceSettings):
|
||
|
|
super().__init__("auth", config)
|
||
|
|
|
||
|
|
def get_service_base_path(self) -> str:
|
||
|
|
"""Return the base path for auth service APIs"""
|
||
|
|
return "/api/v1/auth"
|
||
|
|
|
||
|
|
async def get_user_onboarding_progress(self, user_id: str) -> Optional[Dict[str, Any]]:
|
||
|
|
"""
|
||
|
|
Get user's onboarding progress including step data
|
||
|
|
|
||
|
|
Args:
|
||
|
|
user_id: User ID to fetch progress for
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dict with user progress including steps with data, or None if failed
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
# Use the service endpoint that accepts user_id as parameter
|
||
|
|
result = await self.get(f"/users/{user_id}/onboarding/progress")
|
||
|
|
|
||
|
|
if result:
|
||
|
|
logger.info("Retrieved user onboarding progress",
|
||
|
|
user_id=user_id,
|
||
|
|
current_step=result.get("current_step"))
|
||
|
|
return result
|
||
|
|
else:
|
||
|
|
logger.warning("No onboarding progress found",
|
||
|
|
user_id=user_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get user onboarding progress",
|
||
|
|
user_id=user_id,
|
||
|
|
error=str(e))
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def get_user_step_data(self, user_id: str, step_name: str) -> Optional[Dict[str, Any]]:
|
||
|
|
"""
|
||
|
|
Get data for a specific onboarding step
|
||
|
|
|
||
|
|
Args:
|
||
|
|
user_id: User ID
|
||
|
|
step_name: Name of the step (e.g., "user_registered")
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Step data dictionary or None if not found
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
progress = await self.get_user_onboarding_progress(user_id)
|
||
|
|
|
||
|
|
if not progress:
|
||
|
|
logger.warning("No progress data returned",
|
||
|
|
user_id=user_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
logger.debug("Retrieved progress data",
|
||
|
|
user_id=user_id,
|
||
|
|
steps_count=len(progress.get("steps", [])),
|
||
|
|
current_step=progress.get("current_step"))
|
||
|
|
|
||
|
|
# Find the specific step
|
||
|
|
for step in progress.get("steps", []):
|
||
|
|
if step.get("step_name") == step_name:
|
||
|
|
step_data = step.get("data", {})
|
||
|
|
logger.info("Found step data",
|
||
|
|
user_id=user_id,
|
||
|
|
step_name=step_name,
|
||
|
|
data_keys=list(step_data.keys()) if step_data else [],
|
||
|
|
has_subscription_plan="subscription_plan" in step_data)
|
||
|
|
return step_data
|
||
|
|
|
||
|
|
logger.warning("Step not found in progress",
|
||
|
|
user_id=user_id,
|
||
|
|
step_name=step_name,
|
||
|
|
available_steps=[s.get("step_name") for s in progress.get("steps", [])])
|
||
|
|
return None
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get step data",
|
||
|
|
user_id=user_id,
|
||
|
|
step_name=step_name,
|
||
|
|
error=str(e))
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def get_subscription_plan_from_registration(self, user_id: str) -> str:
|
||
|
|
"""
|
||
|
|
Get the subscription plan selected during user registration
|
||
|
|
|
||
|
|
Args:
|
||
|
|
user_id: User ID
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Plan name (e.g., "starter", "professional", "enterprise") or "starter" as default
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
step_data = await self.get_user_step_data(user_id, "user_registered")
|
||
|
|
|
||
|
|
if step_data and "subscription_plan" in step_data:
|
||
|
|
plan = step_data["subscription_plan"]
|
||
|
|
logger.info("Retrieved subscription plan from registration",
|
||
|
|
user_id=user_id,
|
||
|
|
plan=plan)
|
||
|
|
return plan
|
||
|
|
else:
|
||
|
|
logger.info("No subscription plan in registration data, using default",
|
||
|
|
user_id=user_id,
|
||
|
|
default_plan="starter")
|
||
|
|
return "starter"
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.warning("Failed to retrieve subscription plan, using default",
|
||
|
|
user_id=user_id,
|
||
|
|
error=str(e),
|
||
|
|
default_plan="starter")
|
||
|
|
return "starter"
|
||
|
|
|
||
|
|
async def create_user_by_owner(self, user_data: Dict[str, Any]) -> Dict[str, Any]:
|
||
|
|
"""
|
||
|
|
Create a new user account via the auth service (owner/admin only - pilot phase).
|
||
|
|
|
||
|
|
This method calls the auth service endpoint that allows tenant owners
|
||
|
|
to directly create users with passwords during the pilot phase.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
user_data: Dictionary containing:
|
||
|
|
- email: User email (required)
|
||
|
|
- full_name: Full name (required)
|
||
|
|
- password: Password (required)
|
||
|
|
- phone: Phone number (optional)
|
||
|
|
- role: User role (optional, default: "user")
|
||
|
|
- language: Language preference (optional, default: "es")
|
||
|
|
- timezone: Timezone (optional, default: "Europe/Madrid")
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dict with created user data including user ID
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
Exception if user creation fails
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
logger.info(
|
||
|
|
"Creating user via auth service",
|
||
|
|
email=user_data.get("email"),
|
||
|
|
role=user_data.get("role", "user")
|
||
|
|
)
|
||
|
|
|
||
|
|
result = await self.post("/users/create-by-owner", user_data)
|
||
|
|
|
||
|
|
if result and result.get("id"):
|
||
|
|
logger.info(
|
||
|
|
"User created successfully via auth service",
|
||
|
|
user_id=result.get("id"),
|
||
|
|
email=result.get("email")
|
||
|
|
)
|
||
|
|
return result
|
||
|
|
else:
|
||
|
|
logger.error("User creation returned no user ID")
|
||
|
|
raise Exception("User creation failed: No user ID returned")
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error(
|
||
|
|
"Failed to create user via auth service",
|
||
|
|
email=user_data.get("email"),
|
||
|
|
error=str(e)
|
||
|
|
)
|
||
|
|
raise
|
||
|
|
|
||
|
|
async def get_user_details(self, user_id: str) -> Optional[Dict[str, Any]]:
|
||
|
|
"""
|
||
|
|
Get detailed user information including payment details
|
||
|
|
|
||
|
|
Args:
|
||
|
|
user_id: User ID to fetch details for
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dict with user details including:
|
||
|
|
- id, email, full_name, is_active, is_verified
|
||
|
|
- phone, language, timezone, role
|
||
|
|
- payment_customer_id, default_payment_method_id
|
||
|
|
- created_at, last_login, etc.
|
||
|
|
Returns None if user not found or request fails
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
logger.info("Fetching user details from auth service",
|
||
|
|
user_id=user_id)
|
||
|
|
|
||
|
|
result = await self.get(f"/users/{user_id}")
|
||
|
|
|
||
|
|
if result and result.get("id"):
|
||
|
|
logger.info("Successfully retrieved user details",
|
||
|
|
user_id=user_id,
|
||
|
|
email=result.get("email"),
|
||
|
|
has_payment_info="payment_customer_id" in result)
|
||
|
|
return result
|
||
|
|
else:
|
||
|
|
logger.warning("No user details found",
|
||
|
|
user_id=user_id)
|
||
|
|
return None
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Failed to get user details from auth service",
|
||
|
|
user_id=user_id,
|
||
|
|
error=str(e))
|
||
|
|
return None
|
||
|
|
|
||
|
|
async def update_user_tenant_id(self, user_id: str, tenant_id: str) -> bool:
|
||
|
|
"""
|
||
|
|
Update the user's tenant_id after tenant registration
|
||
|
|
|
||
|
|
Args:
|
||
|
|
user_id: User ID to update
|
||
|
|
tenant_id: Tenant ID to link to the user
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
True if successful, False otherwise
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
logger.info("Updating user tenant_id",
|
||
|
|
user_id=user_id,
|
||
|
|
tenant_id=tenant_id)
|
||
|
|
|
||
|
|
result = await self.patch(
|
||
|
|
f"/users/{user_id}/tenant",
|
||
|
|
{"tenant_id": tenant_id}
|
||
|
|
)
|
||
|
|
|
||
|
|
if result:
|
||
|
|
logger.info("Successfully updated user tenant_id",
|
||
|
|
user_id=user_id,
|
||
|
|
tenant_id=tenant_id)
|
||
|
|
return True
|
||
|
|
else:
|
||
|
|
logger.warning("Failed to update user tenant_id",
|
||
|
|
user_id=user_id,
|
||
|
|
tenant_id=tenant_id)
|
||
|
|
return False
|
||
|
|
|
||
|
|
except Exception as e:
|
||
|
|
logger.error("Error updating user tenant_id",
|
||
|
|
user_id=user_id,
|
||
|
|
tenant_id=tenant_id,
|
||
|
|
error=str(e))
|
||
|
|
return False
|
||
|
|
|