739 lines
35 KiB
Python
739 lines
35 KiB
Python
"""
|
|
Enhanced Authentication Service
|
|
Updated to use repository pattern with dependency injection and improved error handling
|
|
"""
|
|
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Dict, Any, Optional
|
|
|
|
from fastapi import HTTPException, status
|
|
import structlog
|
|
|
|
from app.repositories import UserRepository, TokenRepository
|
|
from app.schemas.auth import UserRegistration, UserLogin, TokenResponse, UserResponse
|
|
from app.models.users import User
|
|
from app.models.tokens import RefreshToken
|
|
from app.core.security import SecurityManager
|
|
from app.utils.subscription_fetcher import SubscriptionFetcher
|
|
from shared.messaging import UnifiedEventPublisher, EVENT_TYPES
|
|
from shared.database.unit_of_work import UnitOfWork
|
|
from shared.database.transactions import transactional
|
|
from shared.database.exceptions import DatabaseError, ValidationError, DuplicateRecordError
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
# Legacy compatibility alias
|
|
AuthService = None # Will be set at the end of the file
|
|
|
|
|
|
class EnhancedAuthService:
|
|
"""Enhanced authentication service using repository pattern"""
|
|
|
|
def __init__(self, database_manager, event_publisher=None):
|
|
"""Initialize service with database manager and optional event publisher"""
|
|
self.database_manager = database_manager
|
|
self.event_publisher = event_publisher
|
|
|
|
async def register_user(
|
|
self,
|
|
user_data: UserRegistration
|
|
) -> TokenResponse:
|
|
"""Register a new user using repository pattern"""
|
|
try:
|
|
async with self.database_manager.get_session() as db_session:
|
|
async with UnitOfWork(db_session) as uow:
|
|
# Register repositories
|
|
user_repo = uow.register_repository("users", UserRepository, User)
|
|
token_repo = uow.register_repository("tokens", TokenRepository, RefreshToken)
|
|
|
|
# Check if user already exists
|
|
existing_user = await user_repo.get_by_email(user_data.email)
|
|
if existing_user:
|
|
raise DuplicateRecordError("User with this email already exists")
|
|
|
|
# Validate password strength
|
|
if not SecurityManager.validate_password(user_data.password):
|
|
raise ValueError("Password does not meet security requirements")
|
|
|
|
# Create user data
|
|
# Default to admin role for first-time registrations during onboarding flow
|
|
# Users creating their own bakery should have admin privileges
|
|
user_role = user_data.role if user_data.role else "admin"
|
|
hashed_password = SecurityManager.hash_password(user_data.password)
|
|
|
|
create_data = {
|
|
"email": user_data.email,
|
|
"full_name": user_data.full_name,
|
|
"hashed_password": hashed_password,
|
|
"is_active": True,
|
|
"is_verified": False,
|
|
"role": user_role
|
|
}
|
|
|
|
# Create user using repository
|
|
new_user = await user_repo.create_user(create_data)
|
|
|
|
logger.debug("User created with repository pattern",
|
|
user_id=new_user.id,
|
|
email=user_data.email,
|
|
role=user_role)
|
|
|
|
# Create tokens with different payloads
|
|
access_token_data = {
|
|
"user_id": str(new_user.id),
|
|
"email": new_user.email,
|
|
"full_name": new_user.full_name,
|
|
"is_verified": new_user.is_verified,
|
|
"is_active": new_user.is_active,
|
|
"role": new_user.role,
|
|
"type": "access"
|
|
}
|
|
|
|
refresh_token_data = {
|
|
"user_id": str(new_user.id),
|
|
"email": new_user.email,
|
|
"type": "refresh"
|
|
}
|
|
|
|
# Generate tokens
|
|
access_token = SecurityManager.create_access_token(user_data=access_token_data)
|
|
refresh_token_value = SecurityManager.create_refresh_token(user_data=refresh_token_data)
|
|
|
|
# Store refresh token using repository
|
|
token_data = {
|
|
"user_id": str(new_user.id),
|
|
"token": refresh_token_value,
|
|
"expires_at": datetime.now(timezone.utc) + timedelta(days=30),
|
|
"is_revoked": False
|
|
}
|
|
|
|
await token_repo.create_token(token_data)
|
|
|
|
# Record GDPR consent if provided
|
|
if (user_data.terms_accepted or user_data.privacy_accepted or
|
|
user_data.marketing_consent or user_data.analytics_consent):
|
|
try:
|
|
from app.models.consent import UserConsent, ConsentHistory
|
|
|
|
ip_address = None # Would need to pass from request context
|
|
user_agent = None # Would need to pass from request context
|
|
|
|
consent = UserConsent(
|
|
user_id=new_user.id,
|
|
terms_accepted=user_data.terms_accepted if user_data.terms_accepted is not None else True,
|
|
privacy_accepted=user_data.privacy_accepted if user_data.privacy_accepted is not None else True,
|
|
marketing_consent=user_data.marketing_consent if user_data.marketing_consent is not None else False,
|
|
analytics_consent=user_data.analytics_consent if user_data.analytics_consent is not None else False,
|
|
consent_version="1.0",
|
|
consent_method="registration",
|
|
ip_address=ip_address,
|
|
user_agent=user_agent,
|
|
consented_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
db_session.add(consent)
|
|
await db_session.flush()
|
|
|
|
# Create consent history entry
|
|
history = ConsentHistory(
|
|
user_id=new_user.id,
|
|
consent_id=consent.id,
|
|
action="granted",
|
|
consent_snapshot={
|
|
"terms_accepted": consent.terms_accepted,
|
|
"privacy_accepted": consent.privacy_accepted,
|
|
"marketing_consent": consent.marketing_consent,
|
|
"analytics_consent": consent.analytics_consent,
|
|
"consent_version": "1.0",
|
|
"consent_method": "registration"
|
|
},
|
|
ip_address=ip_address,
|
|
user_agent=user_agent,
|
|
consent_method="registration",
|
|
created_at=datetime.now(timezone.utc)
|
|
)
|
|
db_session.add(history)
|
|
|
|
logger.info("User consent recorded during registration",
|
|
user_id=new_user.id,
|
|
terms_accepted=consent.terms_accepted,
|
|
privacy_accepted=consent.privacy_accepted,
|
|
marketing_consent=consent.marketing_consent,
|
|
analytics_consent=consent.analytics_consent)
|
|
except Exception as e:
|
|
logger.error("Failed to record user consent during registration",
|
|
user_id=new_user.id,
|
|
error=str(e))
|
|
# Re-raise to ensure registration fails if consent can't be recorded
|
|
raise
|
|
|
|
# Store subscription plan selection in onboarding progress BEFORE committing
|
|
# This ensures it's part of the same transaction
|
|
if user_data.subscription_plan or user_data.use_trial or user_data.payment_method_id:
|
|
try:
|
|
from app.repositories.onboarding_repository import OnboardingRepository
|
|
from app.models.onboarding import UserOnboardingProgress
|
|
|
|
# Use upsert_user_step instead of save_step_data to avoid double commits
|
|
onboarding_repo = OnboardingRepository(db_session)
|
|
plan_data = {
|
|
"subscription_plan": user_data.subscription_plan or "starter",
|
|
"subscription_tier": user_data.subscription_plan or "starter", # Store tier for enterprise onboarding logic
|
|
"use_trial": user_data.use_trial or False,
|
|
"payment_method_id": user_data.payment_method_id,
|
|
"saved_at": datetime.now(timezone.utc).isoformat()
|
|
}
|
|
|
|
# Create the onboarding step record with plan data
|
|
# Note: We use completed=True to mark user_registered as complete
|
|
# auto_commit=False to let UnitOfWork handle the commit
|
|
await onboarding_repo.upsert_user_step(
|
|
user_id=str(new_user.id),
|
|
step_name="user_registered",
|
|
completed=True,
|
|
step_data=plan_data,
|
|
auto_commit=False
|
|
)
|
|
|
|
logger.info("Subscription plan saved to onboarding progress",
|
|
user_id=new_user.id,
|
|
plan=user_data.subscription_plan)
|
|
except Exception as e:
|
|
logger.error("Failed to save subscription plan to onboarding progress",
|
|
user_id=new_user.id,
|
|
error=str(e))
|
|
# Re-raise to ensure registration fails if onboarding data can't be saved
|
|
raise
|
|
|
|
# Commit transaction (includes user, tokens, consent, and onboarding data)
|
|
await uow.commit()
|
|
|
|
# Publish registration event (non-blocking)
|
|
if self.event_publisher:
|
|
try:
|
|
await self.event_publisher.publish_business_event(
|
|
event_type="auth.user.registered",
|
|
tenant_id="system", # User registration is system-wide initially
|
|
data={
|
|
"user_id": str(new_user.id),
|
|
"email": new_user.email,
|
|
"full_name": new_user.full_name,
|
|
"role": new_user.role,
|
|
"registered_at": datetime.now(timezone.utc).isoformat(),
|
|
"subscription_plan": user_data.subscription_plan or "starter"
|
|
}
|
|
)
|
|
except Exception as e:
|
|
logger.warning("Failed to publish registration event: %s", str(e))
|
|
|
|
logger.info("User registered successfully using repository pattern",
|
|
user_id=new_user.id,
|
|
email=user_data.email)
|
|
|
|
from app.schemas.auth import UserData
|
|
return TokenResponse(
|
|
access_token=access_token,
|
|
refresh_token=refresh_token_value,
|
|
token_type="bearer",
|
|
expires_in=1800,
|
|
user=UserData(
|
|
id=str(new_user.id),
|
|
email=new_user.email,
|
|
full_name=new_user.full_name,
|
|
is_active=new_user.is_active,
|
|
is_verified=new_user.is_verified,
|
|
created_at=new_user.created_at.isoformat() if new_user.created_at else datetime.now(timezone.utc).isoformat(),
|
|
role=new_user.role
|
|
)
|
|
)
|
|
|
|
except (ValidationError, DuplicateRecordError):
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Registration failed using repository pattern",
|
|
email=user_data.email,
|
|
error=str(e))
|
|
raise DatabaseError(f"Registration failed: {str(e)}")
|
|
|
|
async def login_user(
|
|
self,
|
|
login_data: UserLogin
|
|
) -> TokenResponse:
|
|
"""Login user using repository pattern"""
|
|
try:
|
|
async with self.database_manager.get_session() as db_session:
|
|
async with UnitOfWork(db_session) as uow:
|
|
# Register repositories
|
|
user_repo = uow.register_repository("users", UserRepository, User)
|
|
token_repo = uow.register_repository("tokens", TokenRepository, RefreshToken)
|
|
|
|
# Authenticate user using repository
|
|
user = await user_repo.authenticate_user(login_data.email, login_data.password)
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid email or password"
|
|
)
|
|
|
|
if not user.is_active:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Account is deactivated"
|
|
)
|
|
|
|
# Revoke existing refresh tokens using repository
|
|
await token_repo.revoke_all_user_tokens(str(user.id))
|
|
|
|
logger.debug("Existing tokens revoked using repository pattern",
|
|
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
|
|
subscription_data = subscription_context.get("subscription") or {}
|
|
|
|
access_token_data = {
|
|
"user_id": str(user.id),
|
|
"email": user.email,
|
|
"full_name": user.full_name,
|
|
"is_verified": user.is_verified,
|
|
"is_active": user.is_active,
|
|
"role": user.role,
|
|
"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 = {
|
|
"user_id": str(user.id),
|
|
"email": user.email,
|
|
"type": "refresh",
|
|
"jti": str(uuid.uuid4())
|
|
}
|
|
|
|
# Generate tokens
|
|
access_token = SecurityManager.create_access_token(user_data=access_token_data)
|
|
refresh_token_value = SecurityManager.create_refresh_token(user_data=refresh_token_data)
|
|
|
|
# Store refresh token using repository
|
|
token_data = {
|
|
"user_id": str(user.id),
|
|
"token": refresh_token_value,
|
|
"expires_at": datetime.now(timezone.utc) + timedelta(days=30),
|
|
"is_revoked": False
|
|
}
|
|
|
|
await token_repo.create_token(token_data)
|
|
|
|
# Update last login using repository
|
|
await user_repo.update_last_login(str(user.id))
|
|
|
|
# Commit transaction
|
|
await uow.commit()
|
|
|
|
# Publish login event (non-blocking)
|
|
if self.event_publisher:
|
|
try:
|
|
await self.event_publisher.publish_business_event(
|
|
event_type="auth.user.login",
|
|
tenant_id="system",
|
|
data={
|
|
"user_id": str(user.id),
|
|
"email": user.email,
|
|
"login_at": datetime.now(timezone.utc).isoformat()
|
|
}
|
|
)
|
|
except Exception as e:
|
|
logger.warning("Failed to publish login event: %s", str(e))
|
|
|
|
logger.info("User logged in successfully using repository pattern",
|
|
user_id=user.id,
|
|
email=login_data.email)
|
|
|
|
from app.schemas.auth import UserData
|
|
return TokenResponse(
|
|
access_token=access_token,
|
|
refresh_token=refresh_token_value,
|
|
token_type="bearer",
|
|
expires_in=1800,
|
|
user=UserData(
|
|
id=str(user.id),
|
|
email=user.email,
|
|
full_name=user.full_name,
|
|
is_active=user.is_active,
|
|
is_verified=user.is_verified,
|
|
created_at=user.created_at.isoformat() if user.created_at else datetime.now(timezone.utc).isoformat(),
|
|
role=user.role
|
|
)
|
|
)
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Login failed using repository pattern",
|
|
email=login_data.email,
|
|
error=str(e))
|
|
raise DatabaseError(f"Login failed: {str(e)}")
|
|
|
|
async def logout_user(self, user_id: str, refresh_token: str) -> bool:
|
|
"""Logout user using repository pattern"""
|
|
try:
|
|
async with self.database_manager.get_session() as session:
|
|
token_repo = TokenRepository(RefreshToken, session)
|
|
|
|
# Revoke specific refresh token using repository
|
|
success = await token_repo.revoke_token(user_id, refresh_token)
|
|
|
|
if success:
|
|
logger.info("User logged out successfully using repository pattern",
|
|
user_id=user_id)
|
|
return True
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error("Logout failed using repository pattern",
|
|
user_id=user_id,
|
|
error=str(e))
|
|
return False
|
|
|
|
async def refresh_access_token(self, refresh_token: str) -> Dict[str, Any]:
|
|
"""Refresh access token using repository pattern"""
|
|
try:
|
|
# Verify refresh token
|
|
payload = SecurityManager.decode_token(refresh_token)
|
|
user_id = payload.get("user_id")
|
|
|
|
if not user_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid refresh token"
|
|
)
|
|
|
|
async with self.database_manager.get_session() as session:
|
|
user_repo = UserRepository(User, session)
|
|
token_repo = TokenRepository(RefreshToken, session)
|
|
|
|
# Validate refresh token using repository
|
|
is_valid = await token_repo.validate_refresh_token(refresh_token, user_id)
|
|
if not is_valid:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid or expired refresh token"
|
|
)
|
|
|
|
# Get user using repository
|
|
user = await user_repo.get_by_id(user_id)
|
|
if not user or not user.is_active:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="User not found or inactive"
|
|
)
|
|
|
|
# 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 = {
|
|
"user_id": str(user.id),
|
|
"email": user.email,
|
|
"full_name": user.full_name,
|
|
"is_verified": user.is_verified,
|
|
"is_active": user.is_active,
|
|
"role": user.role,
|
|
"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)
|
|
|
|
logger.debug("Access token refreshed successfully using repository pattern",
|
|
user_id=user_id)
|
|
|
|
return {
|
|
"access_token": new_access_token,
|
|
"token_type": "bearer",
|
|
"expires_in": 1800
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Token refresh failed using repository pattern: %s", str(e))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Token refresh failed"
|
|
)
|
|
|
|
async def verify_user_token(self, token: str) -> Dict[str, Any]:
|
|
"""Verify access token and return user info"""
|
|
try:
|
|
payload = SecurityManager.verify_token(token)
|
|
if not payload:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid token"
|
|
)
|
|
|
|
# Handle service tokens (used for inter-service communication)
|
|
if payload.get("type") == "service":
|
|
logger.debug("Service token verified successfully",
|
|
service=payload.get("service"),
|
|
tenant_id=payload.get("tenant_id"))
|
|
return {
|
|
"valid": True,
|
|
"user_id": payload.get("user_id", f"{payload.get('service')}-service"),
|
|
"email": payload.get("email", f"{payload.get('service')}-service@internal"),
|
|
"role": payload.get("role", "admin"),
|
|
"exp": payload.get("exp"),
|
|
"service": payload.get("service"),
|
|
"tenant_id": payload.get("tenant_id")
|
|
}
|
|
|
|
# Handle regular user tokens
|
|
return payload
|
|
|
|
except Exception as e:
|
|
logger.error("Token verification error using repository pattern: %s", str(e))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid token"
|
|
)
|
|
|
|
async def get_user_profile(self, user_id: str) -> Optional[UserResponse]:
|
|
"""Get user profile using repository pattern"""
|
|
try:
|
|
async with self.database_manager.get_session() as session:
|
|
user_repo = UserRepository(User, session)
|
|
|
|
user = await user_repo.get_by_id(user_id)
|
|
if not user:
|
|
return None
|
|
|
|
return UserResponse(
|
|
id=str(user.id),
|
|
email=user.email,
|
|
full_name=user.full_name,
|
|
is_active=user.is_active,
|
|
is_verified=user.is_verified,
|
|
created_at=user.created_at,
|
|
role=user.role
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to get user profile using repository pattern",
|
|
user_id=user_id,
|
|
error=str(e))
|
|
return None
|
|
|
|
async def update_user_profile(
|
|
self,
|
|
user_id: str,
|
|
update_data: Dict[str, Any]
|
|
) -> Optional[UserResponse]:
|
|
"""Update user profile using repository pattern"""
|
|
try:
|
|
async with self.database_manager.get_session() as session:
|
|
user_repo = UserRepository(User, session)
|
|
|
|
updated_user = await user_repo.update(user_id, update_data)
|
|
if not updated_user:
|
|
return None
|
|
|
|
logger.info("User profile updated using repository pattern",
|
|
user_id=user_id,
|
|
updated_fields=list(update_data.keys()))
|
|
|
|
return UserResponse(
|
|
id=str(updated_user.id),
|
|
email=updated_user.email,
|
|
full_name=updated_user.full_name,
|
|
is_active=updated_user.is_active,
|
|
is_verified=updated_user.is_verified,
|
|
created_at=updated_user.created_at,
|
|
role=updated_user.role
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to update user profile using repository pattern",
|
|
user_id=user_id,
|
|
error=str(e))
|
|
raise DatabaseError(f"Failed to update profile: {str(e)}")
|
|
|
|
async def change_password(
|
|
self,
|
|
user_id: str,
|
|
old_password: str,
|
|
new_password: str
|
|
) -> bool:
|
|
"""Change user password using repository pattern"""
|
|
try:
|
|
async with self.database_manager.get_session() as session:
|
|
user_repo = UserRepository(User, session)
|
|
token_repo = TokenRepository(RefreshToken, session)
|
|
|
|
# Get user and verify old password
|
|
user = await user_repo.get_by_id(user_id)
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
if not SecurityManager.verify_password(old_password, user.hashed_password):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid old password"
|
|
)
|
|
|
|
# Validate new password strength
|
|
if not SecurityManager.validate_password(new_password):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="New password does not meet security requirements"
|
|
)
|
|
|
|
# Hash new password and update
|
|
new_hashed_password = SecurityManager.hash_password(new_password)
|
|
await user_repo.update(user_id, {"hashed_password": new_hashed_password})
|
|
|
|
# Revoke all existing tokens for security
|
|
await token_repo.revoke_all_user_tokens(user_id)
|
|
|
|
logger.info("Password changed successfully using repository pattern",
|
|
user_id=user_id)
|
|
|
|
return True
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Failed to change password using repository pattern",
|
|
user_id=user_id,
|
|
error=str(e))
|
|
raise DatabaseError(f"Failed to change password: {str(e)}")
|
|
|
|
async def verify_user_email(self, user_id: str, verification_token: str) -> bool:
|
|
"""Verify user email using repository pattern"""
|
|
try:
|
|
async with self.database_manager.get_session() as session:
|
|
user_repo = UserRepository(User, session)
|
|
|
|
# In a real implementation, you'd verify the verification_token
|
|
# For now, just mark user as verified
|
|
updated_user = await user_repo.update(user_id, {"is_verified": True})
|
|
|
|
if updated_user:
|
|
logger.info("User email verified using repository pattern",
|
|
user_id=user_id)
|
|
return True
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to verify email using repository pattern",
|
|
user_id=user_id,
|
|
error=str(e))
|
|
return False
|
|
|
|
async def deactivate_user(self, user_id: str, admin_user_id: str) -> bool:
|
|
"""Deactivate user account using repository pattern"""
|
|
try:
|
|
async with self.database_manager.get_session() as session:
|
|
user_repo = UserRepository(User, session)
|
|
token_repo = TokenRepository(RefreshToken, session)
|
|
|
|
# Update user status
|
|
updated_user = await user_repo.update(user_id, {"is_active": False})
|
|
if not updated_user:
|
|
return False
|
|
|
|
# Revoke all tokens
|
|
await token_repo.revoke_all_user_tokens(user_id)
|
|
|
|
logger.info("User deactivated using repository pattern",
|
|
user_id=user_id,
|
|
admin_user_id=admin_user_id)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error("Failed to deactivate user using repository pattern",
|
|
user_id=user_id,
|
|
error=str(e))
|
|
return False
|
|
|
|
async def _get_service_token(self, tenant_id: Optional[str] = None) -> str:
|
|
"""
|
|
Get service token for inter-service communication.
|
|
This is used to fetch subscription data from tenant service.
|
|
|
|
Args:
|
|
tenant_id: Optional tenant ID for tenant-scoped service operations
|
|
|
|
Returns:
|
|
JWT service token
|
|
"""
|
|
try:
|
|
# Create a proper service token with JWT using SecurityManager
|
|
service_token = SecurityManager.create_service_token("auth-service", tenant_id)
|
|
|
|
logger.debug("Generated service token for tenant service communication", tenant_id=tenant_id)
|
|
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
|
|
AuthService = EnhancedAuthService
|
|
|
|
|
|
class EnhancedUserService(EnhancedAuthService):
|
|
"""User service alias for backward compatibility"""
|
|
pass |