Implémente la Story 1.4 du système d'authentification avec plusieurs couches de protection contre les attaques par force brute. Sécurité backend : - Authentification JWT avec access token (15min) + refresh token (7j) - Rotation automatique des refresh tokens avec détection de replay - Rate limiting progressif par IP (délai Fibonacci après échecs) - Intégration Cloudflare Turnstile CAPTCHA après 5 tentatives - Alerte email à l'utilisateur après blocage temporaire - Isolation multi-tenant (un utilisateur ne peut se connecter que sur son établissement) Frontend : - Page de connexion avec feedback visuel des délais et erreurs - Composant TurnstileCaptcha réutilisable - Gestion d'état auth avec stockage sécurisé des tokens - Tests E2E Playwright pour login, tenant isolation, et activation Infrastructure : - Configuration Symfony Security avec json_login + jwt - Cache pools séparés (filesystem en test, Redis en prod) - NullLoginRateLimiter pour environnement de test (évite blocage CI) - Génération des clés JWT en CI après démarrage du backend
213 lines
7.0 KiB
YAML
213 lines
7.0 KiB
YAML
services:
|
|
# =============================================================================
|
|
# BACKEND API - PHP 8.5 + FrankenPHP
|
|
# =============================================================================
|
|
php:
|
|
build:
|
|
context: ./backend
|
|
dockerfile: Dockerfile
|
|
target: dev
|
|
container_name: classeo_php
|
|
# FrankenPHP charge les variables d'environnement système AVANT que Symfony
|
|
# ne parse le fichier .env. Sans env_file, les variables du .env ne seraient
|
|
# pas disponibles au démarrage de FrankenPHP.
|
|
# Avantage : une seule source de vérité (.env), pas de duplication.
|
|
# Note : les variables dans 'environment:' ci-dessous écrasent celles du .env
|
|
env_file:
|
|
- ./backend/.env
|
|
environment:
|
|
# Overrides pour Docker : les hostnames des services utilisent les noms
|
|
# des containers (db, redis, rabbitmq...) au lieu de localhost
|
|
# APP_ENV peut être overridé en CI pour désactiver le rate limiting (test)
|
|
APP_ENV: ${APP_ENV:-dev}
|
|
DATABASE_URL: postgresql://classeo:classeo@db:5432/classeo_master?serverVersion=18&charset=utf8
|
|
REDIS_URL: redis://redis:6379
|
|
MESSENGER_TRANSPORT_DSN: amqp://guest:guest@rabbitmq:5672/%2f/messages
|
|
MERCURE_URL: http://mercure/.well-known/mercure
|
|
MEILISEARCH_URL: http://meilisearch:7700
|
|
MAILER_DSN: ${MAILER_DSN:-smtp://mailpit:1025}
|
|
ports:
|
|
- "18000:8000" # Port externe 18000 pour eviter conflit
|
|
volumes:
|
|
- ./backend:/app:cached
|
|
- caddy_data:/data
|
|
- caddy_config:/config
|
|
depends_on:
|
|
db:
|
|
condition: service_healthy
|
|
redis:
|
|
condition: service_healthy
|
|
rabbitmq:
|
|
condition: service_healthy
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-f", "http://localhost:8000/api/docs"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# FRONTEND - SvelteKit + Node.js
|
|
# =============================================================================
|
|
frontend:
|
|
build:
|
|
context: ./frontend
|
|
dockerfile: Dockerfile
|
|
target: dev
|
|
container_name: classeo_frontend
|
|
environment:
|
|
# URL de fallback, sera remplacée dynamiquement par le hostname en multi-tenant
|
|
PUBLIC_API_URL: http://localhost:18000/api
|
|
PUBLIC_API_PORT: "18000"
|
|
PUBLIC_BASE_DOMAIN: classeo.local
|
|
PUBLIC_MERCURE_URL: http://localhost:3000/.well-known/mercure
|
|
ports:
|
|
- "5174:5173" # Port externe 5174 pour eviter conflit
|
|
volumes:
|
|
- ./frontend:/app:cached
|
|
- frontend_node_modules:/app/node_modules
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5173/"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# DATABASE - PostgreSQL 18.1
|
|
# =============================================================================
|
|
db:
|
|
image: postgres:18.1-alpine
|
|
container_name: classeo_db
|
|
environment:
|
|
POSTGRES_DB: classeo_master
|
|
POSTGRES_USER: classeo
|
|
POSTGRES_PASSWORD: classeo
|
|
ports:
|
|
- "5433:5432" # Port externe 5433 pour eviter conflit avec PostgreSQL local
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "pg_isready -U classeo -d classeo_master"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# CACHE & SESSIONS - Redis 7.4
|
|
# =============================================================================
|
|
redis:
|
|
image: redis:7.4-alpine
|
|
container_name: classeo_redis
|
|
command: redis-server --appendonly yes
|
|
ports:
|
|
- "6380:6379" # Port externe 6380 pour eviter conflit avec Redis local
|
|
volumes:
|
|
- redis_data:/data
|
|
healthcheck:
|
|
test: ["CMD", "redis-cli", "ping"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 5s
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# MESSAGE QUEUE - RabbitMQ 4.2
|
|
# =============================================================================
|
|
rabbitmq:
|
|
image: rabbitmq:4.2-management-alpine
|
|
container_name: classeo_rabbitmq
|
|
environment:
|
|
RABBITMQ_DEFAULT_USER: guest
|
|
RABBITMQ_DEFAULT_PASS: guest
|
|
ports:
|
|
- "5672:5672"
|
|
- "15672:15672"
|
|
volumes:
|
|
- rabbitmq_data:/var/lib/rabbitmq
|
|
healthcheck:
|
|
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 5
|
|
start_period: 30s
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# REAL-TIME SSE - Mercure
|
|
# =============================================================================
|
|
mercure:
|
|
image: dunglas/mercure:latest
|
|
container_name: classeo_mercure
|
|
environment:
|
|
MERCURE_PUBLISHER_JWT_KEY: "mercure_publisher_secret_change_me_in_production"
|
|
MERCURE_SUBSCRIBER_JWT_KEY: "mercure_subscriber_secret_change_me_in_production"
|
|
SERVER_NAME: ":80"
|
|
MERCURE_EXTRA_DIRECTIVES: |
|
|
cors_origins http://localhost:5174
|
|
anonymous
|
|
ports:
|
|
- "3000:80"
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost/.well-known/mercure"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# FULL-TEXT SEARCH - Meilisearch 1.12
|
|
# =============================================================================
|
|
meilisearch:
|
|
image: getmeili/meilisearch:v1.12
|
|
container_name: classeo_meilisearch
|
|
environment:
|
|
MEILI_MASTER_KEY: "masterKey"
|
|
MEILI_ENV: "development"
|
|
ports:
|
|
- "7700:7700"
|
|
volumes:
|
|
- meilisearch_data:/meili_data
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:7700/health"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# EMAIL TESTING - Mailpit
|
|
# =============================================================================
|
|
mailpit:
|
|
image: axllent/mailpit:latest
|
|
container_name: classeo_mailpit
|
|
ports:
|
|
- "1025:1025"
|
|
- "8025:8025"
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025"]
|
|
interval: 10s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 5s
|
|
restart: unless-stopped
|
|
|
|
# =============================================================================
|
|
# VOLUMES PERSISTANTS
|
|
# =============================================================================
|
|
volumes:
|
|
postgres_data:
|
|
redis_data:
|
|
rabbitmq_data:
|
|
meilisearch_data:
|
|
frontend_node_modules:
|
|
caddy_data:
|
|
caddy_config:
|