Files
Classeo/backend/config/services.yaml
Mathias STRASSER affad287f9 feat: Réinitialisation de mot de passe avec tokens sécurisés
Implémentation complète du flux de réinitialisation de mot de passe (Story 1.5):

Backend:
- Aggregate PasswordResetToken avec TTL 1h, UUID v7, usage unique
- Endpoint POST /api/password/forgot avec rate limiting (3/h par email, 10/h par IP)
- Endpoint POST /api/password/reset avec validation token
- Templates email (demande + confirmation)
- Repository Redis avec TTL 2h pour distinguer expiré/invalide

Frontend:
- Page /mot-de-passe-oublie avec message générique (anti-énumération)
- Page /reset-password/[token] avec validation temps réel des critères
- Gestion erreurs: token invalide, expiré, déjà utilisé

Tests:
- 14 tests unitaires PasswordResetToken
- 7 tests unitaires RequestPasswordResetHandler
- 7 tests unitaires ResetPasswordHandler
- Tests E2E Playwright pour le flux complet
2026-02-02 09:45:15 +01:00

164 lines
7.1 KiB
YAML

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
tenant.base_domain: '%env(TENANT_BASE_DOMAIN)%'
app.url: '%env(APP_URL)%'
services:
# default configuration for services in this file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
# Bind activation tokens cache pool (7-day TTL)
Psr\Cache\CacheItemPoolInterface $activationTokensCache: '@activation_tokens.cache'
# Bind users cache pool (no TTL - persistent data)
Psr\Cache\CacheItemPoolInterface $usersCache: '@users.cache'
# Bind refresh tokens cache pool (7-day TTL)
Psr\Cache\CacheItemPoolInterface $refreshTokensCache: '@refresh_tokens.cache'
# Bind password reset tokens cache pool (1-hour TTL)
Psr\Cache\CacheItemPoolInterface $passwordResetTokensCache: '@password_reset_tokens.cache'
# Bind named message buses
Symfony\Component\Messenger\MessageBusInterface $eventBus: '@event.bus'
Symfony\Component\Messenger\MessageBusInterface $commandBus: '@command.bus'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# Exclude Domain layers - they should be pure PHP with no framework deps
- '../src/*/Domain/'
# Domain services need to be registered explicitly to avoid framework coupling
# Example: App\Administration\Application\Command\:
# resource: '../src/Administration/Application/Command/'
# Tenant services
App\Shared\Infrastructure\Tenant\TenantResolver:
arguments:
$baseDomain: '%tenant.base_domain%'
# TenantRegistry est configuré par environnement :
# - dev: config/packages/dev/tenant.yaml (tenants de test)
# - prod: à configurer via admin ou env vars
App\Shared\Infrastructure\Tenant\Command\CreateTenantDatabaseCommand:
arguments:
$masterDatabaseUrl: '%env(DATABASE_URL)%'
App\Shared\Infrastructure\Tenant\Command\TenantMigrateCommand:
arguments:
$projectDir: '%kernel.project_dir%'
# Administration services
# Bind Repository interfaces to their implementations
App\Administration\Domain\Repository\ActivationTokenRepository:
alias: App\Administration\Infrastructure\Persistence\Redis\RedisActivationTokenRepository
App\Administration\Domain\Repository\UserRepository:
alias: App\Administration\Infrastructure\Persistence\Cache\CacheUserRepository
App\Administration\Application\Port\PasswordHasher:
alias: App\Administration\Infrastructure\Security\SymfonyPasswordHasher
# Clock interface binding
App\Shared\Domain\Clock:
alias: App\Shared\Infrastructure\Clock\SystemClock
# Domain policies (need explicit registration as Domain is excluded from autowiring)
App\Administration\Domain\Policy\ConsentementParentalPolicy:
autowire: true
# Email handlers
App\Administration\Infrastructure\Messaging\SendActivationConfirmationHandler:
arguments:
$appUrl: '%app.url%'
App\Administration\Infrastructure\Messaging\SendPasswordResetEmailHandler:
arguments:
$appUrl: '%app.url%'
App\Administration\Infrastructure\Messaging\SendPasswordResetConfirmationHandler:
arguments:
$appUrl: '%app.url%'
# Audit log handler (uses dedicated audit channel)
App\Administration\Infrastructure\Messaging\AuditLoginEventsHandler:
arguments:
$auditLogger: '@monolog.logger.audit'
$appSecret: '%env(APP_SECRET)%'
# JWT Authentication
App\Administration\Infrastructure\Security\JwtPayloadEnricher:
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_created, method: onJWTCreated }
App\Administration\Infrastructure\Security\DatabaseUserProvider:
arguments:
$userRepository: '@App\Administration\Domain\Repository\UserRepository'
# Refresh Token Repository
App\Administration\Domain\Repository\RefreshTokenRepository:
alias: App\Administration\Infrastructure\Persistence\Redis\RedisRefreshTokenRepository
# Password Reset Token Repository
App\Administration\Domain\Repository\PasswordResetTokenRepository:
alias: App\Administration\Infrastructure\Persistence\Redis\RedisPasswordResetTokenRepository
# Password Reset Processor with rate limiters
App\Administration\Infrastructure\Api\Processor\RequestPasswordResetProcessor:
arguments:
$passwordResetByEmailLimiter: '@limiter.password_reset_by_email'
$passwordResetByIpLimiter: '@limiter.password_reset_by_ip'
# Login handlers
App\Administration\Infrastructure\Security\LoginSuccessHandler:
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccess }
App\Administration\Infrastructure\Security\LoginFailureHandler:
tags:
- { name: security.authentication_failure_handler, firewall: api_login }
# Rate Limiter (délai Fibonacci + CAPTCHA + blocage IP)
App\Shared\Infrastructure\RateLimit\LoginRateLimiter:
arguments:
$cache: '@cache.rate_limiter'
App\Shared\Infrastructure\RateLimit\LoginRateLimiterInterface:
alias: App\Shared\Infrastructure\RateLimit\LoginRateLimiter
# Rate Limit Listener (vérifie le rate limit AVANT authentification)
App\Shared\Infrastructure\RateLimit\LoginRateLimitListener:
arguments:
$rateLimiterCache: '@cache.rate_limiter'
# Turnstile CAPTCHA Validator
# failOpen: true en dev (ne pas bloquer si API down), false en prod (sécurité)
App\Shared\Infrastructure\Captcha\TurnstileValidator:
arguments:
$secretKey: '%env(TURNSTILE_SECRET_KEY)%'
$failOpen: '%env(bool:default::TURNSTILE_FAIL_OPEN)%'
App\Shared\Infrastructure\Captcha\TurnstileValidatorInterface:
alias: App\Shared\Infrastructure\Captcha\TurnstileValidator
# =============================================================================
# Test environment overrides
# =============================================================================
when@test:
services:
# Use null rate limiter in test environment to avoid IP blocking during E2E tests
App\Shared\Infrastructure\RateLimit\LoginRateLimiterInterface:
alias: App\Shared\Infrastructure\RateLimit\NullLoginRateLimiter
App\Shared\Infrastructure\RateLimit\NullLoginRateLimiter:
autowire: true