Files
metabuilder/deployment/docker-compose.yml
johndoe6345789 df5398a7ee feat(auth): Phase 7 Flask authentication middleware with JWT and multi-tenant isolation
Complete implementation of enterprise-grade authentication middleware for email service:

Features:
- JWT token creation/validation with configurable expiration
- Bearer token extraction and validation
- Multi-tenant isolation enforced at middleware level
- Role-based access control (RBAC) with user/admin roles
- Row-level security (RLS) for resource access
- Automatic request logging with user context and audit trail
- CORS configuration for email client frontend
- Rate limiting (50 req/min per user with Redis backend)
- Comprehensive error handling with proper HTTP status codes

Implementation:
- Enhanced src/middleware/auth.py (415 lines)
  - JWTConfig class for token management
  - create_jwt_token() for token generation
  - decode_jwt_token() for token validation
  - @verify_tenant_context decorator for auth middleware
  - @verify_role decorator for RBAC
  - verify_resource_access() for row-level security
  - log_request_context() for audit logging

Testing:
- 52 comprehensive test cases covering all features
- 100% pass rate with fast execution (0.15s)
- Test categories: JWT, multi-tenant, RBAC, RLS, logging, integration
- Full coverage of error scenarios and edge cases

Documentation:
- AUTH_MIDDLEWARE.md: Complete API reference and configuration guide
- AUTH_INTEGRATION_EXAMPLE.py: Real-world usage examples for 5+ scenarios
- PHASE_7_SUMMARY.md: Implementation summary with checklist
- Inline code documentation with type hints

Security:
- Multi-tenant data isolation at all levels
- Constant-time password comparison
- JWT signature validation
- CORS protection
- Rate limiting against abuse
- Comprehensive audit logging

Dependencies Added:
- PyJWT==2.8.1

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-24 00:20:19 +00:00

560 lines
17 KiB
YAML

# Phase 8: Email Client Docker Compose Orchestration
# Production-ready email client stack with:
# - Next.js frontend (emailclient)
# - Python email service (Flask + Celery)
# - PostgreSQL database with email schemas
# - Redis cache & message broker
# - Postfix SMTP relay
# - Dovecot IMAP server
# - Nginx reverse proxy with SSL termination
#
# Networks:
# - public: Nginx (ingress only)
# - internal: Backend services (no external access)
#
# Usage:
# # Development
# docker compose up -d
#
# # Production
# docker compose -f docker-compose.yml up -d
#
# # View logs
# docker compose logs -f emailclient
# docker compose logs -f email-service
#
# # Stop all services
# docker compose down
#
# Environment Setup:
# 1. Copy .env.example to .env
# 2. Update sensitive values (passwords, API keys)
# 3. Run docker compose up
version: '3.8'
services:
# ============================================================================
# Reverse Proxy - Nginx with SSL/TLS
# ============================================================================
nginx:
image: nginx:1.27-alpine
container_name: emailclient-nginx
restart: unless-stopped
ports:
- "80:80" # HTTP (redirects to HTTPS)
- "443:443" # HTTPS
volumes:
- ./config/nginx/production.conf:/etc/nginx/nginx.conf:ro
- ./config/nginx/ssl:/etc/nginx/ssl:ro
- nginx_cache:/var/cache/nginx
- nginx_logs:/var/log/nginx
environment:
NGINX_WORKER_PROCESSES: auto
NGINX_WORKER_CONNECTIONS: 2048
depends_on:
emailclient:
condition: service_healthy
networks:
- public
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
# ============================================================================
# Email Client Frontend - Next.js Application
# ============================================================================
emailclient:
build:
context: ../emailclient
dockerfile: Dockerfile
args:
NODE_ENV: production
image: emailclient:latest
container_name: emailclient-app
restart: unless-stopped
environment:
# Next.js Configuration
NODE_ENV: production
PORT: 3000
NEXT_PUBLIC_API_URL: "${EMAILCLIENT_API_URL:-http://localhost}"
NEXT_PUBLIC_WS_URL: "${EMAILCLIENT_WS_URL:-ws://localhost}"
# Email Service Configuration
EMAIL_SERVICE_HOST: email-service
EMAIL_SERVICE_PORT: 5000
EMAIL_SERVICE_URL: http://email-service:5000
# Redis Configuration
REDIS_URL: "redis://:${REDIS_PASSWORD:-emailclient_default_password}@redis:6379/0"
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 0
# PostgreSQL Configuration
DATABASE_URL: "postgresql://${POSTGRES_USER:-emailclient}:${POSTGRES_PASSWORD:-emailclient_default_password}@postgres:5432/${POSTGRES_DB:-emailclient}"
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_USER: "${POSTGRES_USER:-emailclient}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-emailclient_default_password}"
POSTGRES_DB: "${POSTGRES_DB:-emailclient}"
# Logging
LOG_LEVEL: "${LOG_LEVEL:-info}"
# Session & Security
SESSION_SECRET: "${SESSION_SECRET:-change_me_in_production}"
ENCRYPTION_KEY: "${ENCRYPTION_KEY:-change_me_in_production}"
# CORS Configuration
CORS_ORIGINS: "${CORS_ORIGINS:-localhost:3000,localhost:80}"
volumes:
- emailclient_uploads:/app/uploads
- emailclient_cache:/app/.cache
- emailclient_sessions:/app/.sessions
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
email-service:
condition: service_healthy
networks:
- public
- internal
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
# ============================================================================
# Email Service - Python Flask Backend
# ============================================================================
email-service:
build:
context: ../services/email_service
dockerfile: Dockerfile
image: emailclient-service:latest
container_name: emailclient-email-service
restart: unless-stopped
environment:
# Flask Configuration
FLASK_ENV: "${FLASK_ENV:-production}"
FLASK_HOST: 0.0.0.0
FLASK_PORT: 5000
FLASK_DEBUG: "${FLASK_DEBUG:-0}"
# Database Configuration
DATABASE_URL: "postgresql://${POSTGRES_USER:-emailclient}:${POSTGRES_PASSWORD:-emailclient_default_password}@postgres:5432/${POSTGRES_DB:-emailclient}"
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_USER: "${POSTGRES_USER:-emailclient}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-emailclient_default_password}"
POSTGRES_DB: "${POSTGRES_DB:-emailclient}"
# Redis Configuration
REDIS_URL: "redis://:${REDIS_PASSWORD:-emailclient_default_password}@redis:6379/1"
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 1
# Celery Configuration
CELERY_BROKER_URL: "redis://:${REDIS_PASSWORD:-emailclient_default_password}@redis:6379/2"
CELERY_RESULT_BACKEND: "redis://:${REDIS_PASSWORD:-emailclient_default_password}@redis:6379/3"
CELERY_WORKER_CONCURRENCY: "${CELERY_WORKER_CONCURRENCY:-4}"
# Email Server Configuration
POSTFIX_HOST: postfix
POSTFIX_PORT: 25
DOVECOT_HOST: dovecot
DOVECOT_IMAP_PORT: 143
DOVECOT_IMAPS_PORT: 993
DOVECOT_POP3_PORT: 110
DOVECOT_POP3S_PORT: 995
# Email Service Configuration
EMAIL_SERVICE_SECRET: "${EMAIL_SERVICE_SECRET:-change_me_in_production}"
EMAIL_MAX_ATTACHMENT_SIZE: "${EMAIL_MAX_ATTACHMENT_SIZE:-52428800}" # 50MB
EMAIL_SYNC_INTERVAL: "${EMAIL_SYNC_INTERVAL:-300}" # 5 minutes
EMAIL_RATE_LIMIT: "${EMAIL_RATE_LIMIT:-100}"
# CORS Configuration
CORS_ORIGINS: "${CORS_ORIGINS:-http://localhost:3000,http://localhost}"
# Logging
EMAIL_SERVICE_LOG_LEVEL: "${LOG_LEVEL:-INFO}"
volumes:
- email_service_logs:/var/log/email-service
- email_attachments:/data/attachments
- email_temp:/tmp/email-service
ports:
- "5000:5000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postfix:
condition: service_started
dovecot:
condition: service_started
networks:
- internal
healthcheck:
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:5000/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
deploy:
resources:
limits:
cpus: '1.5'
memory: 1G
reservations:
cpus: '0.5'
memory: 256M
# ============================================================================
# Celery Worker - Background Job Processing
# ============================================================================
celery-worker:
build:
context: ../services/email_service
dockerfile: Dockerfile
target: celery
image: emailclient-celery:latest
container_name: emailclient-celery-worker
restart: unless-stopped
environment:
# Flask Configuration
FLASK_ENV: "${FLASK_ENV:-production}"
FLASK_HOST: 0.0.0.0
FLASK_PORT: 5000
FLASK_DEBUG: "${FLASK_DEBUG:-0}"
# Database Configuration
DATABASE_URL: "postgresql://${POSTGRES_USER:-emailclient}:${POSTGRES_PASSWORD:-emailclient_default_password}@postgres:5432/${POSTGRES_DB:-emailclient}"
POSTGRES_HOST: postgres
POSTGRES_PORT: 5432
POSTGRES_USER: "${POSTGRES_USER:-emailclient}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-emailclient_default_password}"
POSTGRES_DB: "${POSTGRES_DB:-emailclient}"
# Redis Configuration
REDIS_URL: "redis://:${REDIS_PASSWORD:-emailclient_default_password}@redis:6379/1"
REDIS_HOST: redis
REDIS_PORT: 6379
REDIS_DB: 1
# Celery Configuration
CELERY_BROKER_URL: "redis://:${REDIS_PASSWORD:-emailclient_default_password}@redis:6379/2"
CELERY_RESULT_BACKEND: "redis://:${REDIS_PASSWORD:-emailclient_default_password}@redis:6379/3"
CELERY_WORKER_CONCURRENCY: "${CELERY_WORKER_CONCURRENCY:-4}"
CELERY_WORKER_PREFETCH_MULTIPLIER: "1"
CELERY_WORKER_MAX_TASKS_PER_CHILD: "1000"
# Email Server Configuration
POSTFIX_HOST: postfix
POSTFIX_PORT: 25
DOVECOT_HOST: dovecot
DOVECOT_IMAP_PORT: 143
DOVECOT_IMAPS_PORT: 993
# Email Service Configuration
EMAIL_SERVICE_SECRET: "${EMAIL_SERVICE_SECRET:-change_me_in_production}"
EMAIL_MAX_ATTACHMENT_SIZE: "${EMAIL_MAX_ATTACHMENT_SIZE:-52428800}"
EMAIL_SYNC_INTERVAL: "${EMAIL_SYNC_INTERVAL:-300}"
# Logging
EMAIL_SERVICE_LOG_LEVEL: "${LOG_LEVEL:-INFO}"
volumes:
- email_service_logs:/var/log/email-service
- email_attachments:/data/attachments
- email_temp:/tmp/email-service
depends_on:
- postgres
- redis
- email-service
networks:
- internal
healthcheck:
test: ["CMD", "celery", "-A", "tasks.celery_app", "inspect", "active"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
cpus: '2'
memory: 1.5G
reservations:
cpus: '0.5'
memory: 512M
# ============================================================================
# PostgreSQL Database - Multi-tenant Email Metadata
# ============================================================================
postgres:
image: postgres:16-alpine
container_name: emailclient-postgres
restart: unless-stopped
environment:
POSTGRES_USER: "${POSTGRES_USER:-emailclient}"
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-emailclient_default_password}"
POSTGRES_DB: "${POSTGRES_DB:-emailclient}"
POSTGRES_INITDB_ARGS: "-E UTF8 --locale=C.UTF-8"
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/init-email-db.sql:/docker-entrypoint-initdb.d/01-init-email-db.sql:ro
ports:
- "5432:5432"
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-emailclient} -d ${POSTGRES_DB:-emailclient}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
# ============================================================================
# Redis Cache & Message Broker
# ============================================================================
redis:
image: redis:7-alpine
container_name: emailclient-redis
restart: unless-stopped
command: >
redis-server
--requirepass ${REDIS_PASSWORD:-emailclient_default_password}
--appendonly yes
--appendfsync everysec
--maxmemory ${REDIS_MAX_MEMORY:-512mb}
--maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
ports:
- "6379:6379"
networks:
- internal
healthcheck:
test: ["CMD", "redis-cli", "--raw", "ping"]
interval: 10s
timeout: 3s
retries: 5
start_period: 5s
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
# ============================================================================
# Postfix SMTP Relay - Outgoing Email
# ============================================================================
postfix:
image: boky/postfix:latest
container_name: emailclient-postfix
restart: unless-stopped
environment:
# Postfix Configuration
ALLOWED_SENDER_DOMAINS: "${POSTFIX_ALLOWED_DOMAINS:-example.com localhost}"
RELAYHOST: "${POSTFIX_RELAYHOST:-}"
RELAYHOST_USERNAME: "${POSTFIX_RELAYHOST_USERNAME:-}"
RELAYHOST_PASSWORD: "${POSTFIX_RELAYHOST_PASSWORD:-}"
POSTFIX_myhostname: "${POSTFIX_HOSTNAME:-emailclient.local}"
POSTFIX_mynetworks: "127.0.0.0/8 10.0.0.0/8 172.16.0.0/12"
POSTFIX_message_size_limit: "${POSTFIX_MESSAGE_SIZE_LIMIT:-52428800}"
POSTFIX_smtpd_recipient_limit: "${POSTFIX_RECIPIENT_LIMIT:-10000}"
# TLS Configuration
POSTFIX_smtpd_use_tls: "yes"
POSTFIX_smtpd_tls_cert_file: "/etc/postfix/ssl/postfix.crt"
POSTFIX_smtpd_tls_key_file: "/etc/postfix/ssl/postfix.key"
volumes:
- postfix_queue:/var/spool/postfix
- postfix_logs:/var/log/postfix
- ./config/postfix/tls:/etc/postfix/ssl:ro
ports:
- "25:25" # SMTP (internal only, accessed via docker network)
- "587:587" # SMTP TLS
networks:
- internal
healthcheck:
test: ["CMD", "postfix", "-c", "/etc/postfix", "status"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
# ============================================================================
# Dovecot IMAP/POP3 Server - Incoming Email & Storage
# ============================================================================
dovecot:
image: dovecot/dovecot:latest
container_name: emailclient-dovecot
restart: unless-stopped
environment:
# Dovecot Configuration
DOVECOT_PROTOCOLS: "imap pop3"
DOVECOT_MAIL_HOME: "/var/mail"
DOVECOT_USER_DB: "static"
DOVECOT_PASS_DB: "static"
DOVECOT_DEFAULT_LOGIN_USER: "dovecot"
DOVECOT_DEFAULT_INTERNAL_USER: "dovecot"
DOVECOT_MAIL_LOCATION: "mbox:~/Mail:INBOX=/var/mail/INBOX"
DOVECOT_SSL: "required"
DOVECOT_SSL_CERT: "/etc/dovecot/ssl/dovecot.crt"
DOVECOT_SSL_KEY: "/etc/dovecot/ssl/dovecot.key"
volumes:
- dovecot_data:/var/mail
- dovecot_config:/etc/dovecot
- dovecot_logs:/var/log/dovecot
- ./config/dovecot/tls:/etc/dovecot/ssl:ro
ports:
- "143:143" # IMAP
- "993:993" # IMAPS
- "110:110" # POP3
- "995:995" # POP3S
networks:
- internal
healthcheck:
test: ["CMD", "doveadm", "ping"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
deploy:
resources:
limits:
cpus: '1'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
# ============================================================================
# Networks
# ============================================================================
networks:
# Public network - only Nginx exposed
public:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24
# Internal network - backend services only
internal:
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/24
# ============================================================================
# Volumes
# ============================================================================
volumes:
# Application Volumes
emailclient_uploads:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: "size=5G,mode=1777"
emailclient_cache:
driver: local
emailclient_sessions:
driver: local
# Database Volumes
postgres_data:
driver: local
# Cache & Message Queue
redis_data:
driver: local
# Email Server Volumes
postfix_queue:
driver: local
postfix_logs:
driver: local
dovecot_data:
driver: local
dovecot_config:
driver: local
dovecot_logs:
driver: local
# Email Service Volumes
email_service_logs:
driver: local
email_attachments:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: "size=10G,mode=1777"
email_temp:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: "size=5G,mode=1777"
# Reverse Proxy Volumes
nginx_cache:
driver: local
driver_opts:
type: tmpfs
device: tmpfs
o: "size=2G,mode=1777"
nginx_logs:
driver: local