
- Implemented comprehensive multi-tenant data isolation using database-level security - Built JWT authentication system with role-based access control (Super Admin, Org Admin, User, Viewer) - Created RESTful API endpoints for user and organization operations - Added complete audit logging for all data modifications with IP tracking - Implemented API rate limiting and input validation with security middleware - Built webhook processing engine with async event handling and retry logic - Created external API call handlers with circuit breaker pattern and error handling - Implemented data synchronization between external services and internal data - Added integration health monitoring and status tracking - Created three mock external services (User Management, Payment, Communication) - Implemented idempotency for webhook processing to handle duplicates gracefully - Added comprehensive security headers and XSS/CSRF protection - Set up Alembic database migrations with proper SQLite configuration - Included extensive documentation and API examples Architecture features: - Multi-tenant isolation at database level - Circuit breaker pattern for external API resilience - Async background task processing - Complete audit trail with user context - Role-based permission system - Webhook signature verification - Request validation and sanitization - Health monitoring endpoints Co-Authored-By: Claude <noreply@anthropic.com>
166 lines
5.4 KiB
Python
166 lines
5.4 KiB
Python
from fastapi import HTTPException, status, Request
|
|
from sqlalchemy.orm import Session
|
|
from typing import Dict, Any
|
|
import logging
|
|
from app.services.webhook import WebhookService
|
|
from app.schemas.webhook import WebhookEventCreate
|
|
from app.models.integration import IntegrationType
|
|
from app.core.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class WebhookHandler:
|
|
def __init__(self, db: Session):
|
|
self.db = db
|
|
self.webhook_service = WebhookService(db)
|
|
|
|
async def handle_user_webhook(
|
|
self,
|
|
request: Request,
|
|
organization_id: int,
|
|
payload: Dict[str, Any]
|
|
):
|
|
"""Handle webhooks from user management service"""
|
|
|
|
# Verify webhook signature
|
|
signature = request.headers.get("x-webhook-signature")
|
|
if not signature:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Missing webhook signature"
|
|
)
|
|
|
|
body = await request.body()
|
|
if not self.webhook_service.verify_webhook_signature(
|
|
body, signature, settings.WEBHOOK_SECRET
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid webhook signature"
|
|
)
|
|
|
|
# Extract event data
|
|
event_id = payload.get("event_id")
|
|
event_type = payload.get("event_type")
|
|
|
|
if not event_id or not event_type:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Missing required fields: event_id, event_type"
|
|
)
|
|
|
|
# Create webhook event
|
|
webhook_data = WebhookEventCreate(
|
|
external_id=event_id,
|
|
event_type=event_type,
|
|
payload=payload,
|
|
integration_type=IntegrationType.USER_MANAGEMENT,
|
|
organization_id=organization_id
|
|
)
|
|
|
|
webhook_event = self.webhook_service.create_webhook_event(webhook_data)
|
|
|
|
logger.info(f"User webhook received: {event_type} - {event_id}")
|
|
|
|
return {"status": "accepted", "webhook_id": webhook_event.id}
|
|
|
|
async def handle_payment_webhook(
|
|
self,
|
|
request: Request,
|
|
organization_id: int,
|
|
payload: Dict[str, Any]
|
|
):
|
|
"""Handle webhooks from payment service"""
|
|
|
|
# Verify webhook signature
|
|
signature = request.headers.get("x-webhook-signature")
|
|
if not signature:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Missing webhook signature"
|
|
)
|
|
|
|
body = await request.body()
|
|
if not self.webhook_service.verify_webhook_signature(
|
|
body, signature, settings.WEBHOOK_SECRET
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid webhook signature"
|
|
)
|
|
|
|
# Extract event data
|
|
event_id = payload.get("event_id")
|
|
event_type = payload.get("event_type")
|
|
|
|
if not event_id or not event_type:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Missing required fields: event_id, event_type"
|
|
)
|
|
|
|
# Create webhook event
|
|
webhook_data = WebhookEventCreate(
|
|
external_id=event_id,
|
|
event_type=event_type,
|
|
payload=payload,
|
|
integration_type=IntegrationType.PAYMENT,
|
|
organization_id=organization_id
|
|
)
|
|
|
|
webhook_event = self.webhook_service.create_webhook_event(webhook_data)
|
|
|
|
logger.info(f"Payment webhook received: {event_type} - {event_id}")
|
|
|
|
return {"status": "accepted", "webhook_id": webhook_event.id}
|
|
|
|
async def handle_communication_webhook(
|
|
self,
|
|
request: Request,
|
|
organization_id: int,
|
|
payload: Dict[str, Any]
|
|
):
|
|
"""Handle webhooks from communication service"""
|
|
|
|
# Verify webhook signature
|
|
signature = request.headers.get("x-webhook-signature")
|
|
if not signature:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Missing webhook signature"
|
|
)
|
|
|
|
body = await request.body()
|
|
if not self.webhook_service.verify_webhook_signature(
|
|
body, signature, settings.WEBHOOK_SECRET
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid webhook signature"
|
|
)
|
|
|
|
# Extract event data
|
|
event_id = payload.get("event_id")
|
|
event_type = payload.get("event_type")
|
|
|
|
if not event_id or not event_type:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Missing required fields: event_id, event_type"
|
|
)
|
|
|
|
# Create webhook event
|
|
webhook_data = WebhookEventCreate(
|
|
external_id=event_id,
|
|
event_type=event_type,
|
|
payload=payload,
|
|
integration_type=IntegrationType.COMMUNICATION,
|
|
organization_id=organization_id
|
|
)
|
|
|
|
webhook_event = self.webhook_service.create_webhook_event(webhook_data)
|
|
|
|
logger.info(f"Communication webhook received: {event_type} - {event_id}")
|
|
|
|
return {"status": "accepted", "webhook_id": webhook_event.id} |