Aller au contenu

PD-19 — Decomposition des tâches agents

Contexte

Ce document décompose le plan d'implémentation PD-19 en tâches assignées aux agents spécialisés pour l'étape 6 du workflow de gouvernance.

Documents de référence : - PD-19-specification.md — Spécification fonctionnelle - PD-19-tests.md — Scénarios de tests contractuels - PD-19-plan.md — Plan d'implémentation - PD-19-code-contracts.yaml — Contrats d'interface


Architecture existante

Composant Fichier État
RateLimitService src/modules/auth/services/rate-limit.service.ts Existe (registration, login, profile)
RateLimitGuard src/modules/auth/guards/rate-limit.guard.ts Existe
ConfigSchema src/config/config.schema.ts Existe (à étendre)
main.ts src/main.ts Helmet/CORS basiques à remplacer
Middleware directory src/common/middleware/ N'existe pas (à créer)

Tâches agent-developer

TASK-DEV-01 — Configuration Security Headers

Fichier : src/config/security-headers.config.ts (CRÉER)

Entrées : - PD-19-code-contracts.yaml section security_headers_config - INV-01, INV-02

Sortie attendue :

// Configuration du socle de security headers par environnement
export const securityHeadersConfig = registerAs('securityHeaders', () => ({
  xContentTypeOptions: 'nosniff',
  xFrameOptions: 'DENY',
  contentSecurityPolicy: "default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",
  referrerPolicy: 'no-referrer',
  permissionsPolicy: 'geolocation=(), camera=(), microphone=(), payment=(), usb=()',
  strictTransportSecurity: {
    maxAge: /* 31536000 prod/test, 300 dev */,
    includeSubDomains: true,
    enabled: /* true sauf dev sans HTTPS */,
  },
  cacheControl: {
    sensitiveRoutes: ['auth/*', 'users/me'],
    sensitiveStatuses: [401, 403],
    value: 'no-store',
  },
  removeHeaders: ['Server', 'X-Powered-By'],
}));


TASK-DEV-02 — Configuration CORS

Fichier : src/config/cors.config.ts (CRÉER)

Entrées : - PD-19-code-contracts.yaml section cors_config - INV-03, INV-04, INV-05

Sortie attendue :

export const corsConfig = registerAs('cors', () => ({
  origins: {
    production: ['https://app.probatiovault.com', 'https://www.probatiovault.com'],
    test: ['https://app-test.probatiovault.com', 'https://site-test.probatiovault.com'],
    development: ['http://localhost:3000', 'http://localhost:8080'],
  },
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Authorization', 'Content-Type', 'Accept', 'Origin', 'X-Requested-With', 'X-Correlation-Id'],
  exposedHeaders: ['X-Correlation-Id', 'Retry-After'],
  credentials: true,
  maxAge: { production: 600, test: 300, development: 60 },
}));


TASK-DEV-03 — Extension rate-limit.config.ts

Fichier : src/config/rate-limit.config.ts (MODIFIER)

Entrées : - PD-19-code-contracts.yaml section rate_limit_config - INV-06

Modification : Ajouter la section global :

global: {
  max: Number.parseInt(process.env.RATE_LIMIT_GLOBAL_MAX || '100', 10),
  windowSeconds: Number.parseInt(process.env.RATE_LIMIT_GLOBAL_WINDOW_S || '60', 10),
  keyPrefix: 'rl:global:',
},
trustedProxies: process.env.TRUSTED_PROXIES || 'loopback',


TASK-DEV-04 — Extension config.schema.ts

Fichier : src/config/config.schema.ts (MODIFIER)

Entrées : - Plan §3 Phase 1 - ERR-05 (fail-fast)

Modifications :

// Section CORS (nouvelle)
CORS_ORIGINS: Joi.string()
  .required()
  .description('CORS allowed origins (comma-separated)'),

// Section Rate Limiting Global (nouvelle)
RATE_LIMIT_GLOBAL_MAX: Joi.number()
  .integer()
  .min(1)
  .default(trackedDefault('RATE_LIMIT_GLOBAL_MAX', 100))
  .description('Global rate limit max requests'),

RATE_LIMIT_GLOBAL_WINDOW_S: Joi.number()
  .integer()
  .min(1)
  .default(trackedDefault('RATE_LIMIT_GLOBAL_WINDOW_S', 60))
  .description('Global rate limit window in seconds'),

TRUSTED_PROXIES: Joi.string()
  .default(trackedDefault('TRUSTED_PROXIES', 'loopback'))
  .description('Trusted proxies for X-Forwarded-For resolution'),


TASK-DEV-05 — SecurityHeadersMiddleware

Fichier : src/common/middleware/security-headers.middleware.ts (CRÉER)

Entrées : - PD-19-code-contracts.yaml section security_headers_middleware - INV-01, INV-02, INV-11

Comportement requis : 1. Appliquer tous les headers contractuels via res.setHeader() 2. Supprimer Server et X-Powered-By via res.removeHeader() 3. Utiliser res.on('finish', ...) pour garantir Cache-Control: no-store sur : - Routes /auth/* et /users/me - Statuts 401 et 403 4. S'exécuter sur TOUTE réponse (y compris erreurs)

Contrat :

@Injectable()
export class SecurityHeadersMiddleware implements NestMiddleware {
  constructor(private readonly configService: ConfigService) {}

  use(req: Request, res: Response, next: NextFunction): void;
}


TASK-DEV-06 — CorsMiddleware

Fichier : src/common/middleware/cors.middleware.ts (CRÉER)

Entrées : - PD-19-code-contracts.yaml section cors_middleware - INV-03, INV-04, INV-05, INV-08, INV-09

Comportement requis : 1. Extraire Origin de la requête 2. Valider contre la liste d'origines de l'environnement courant 3. Si autorisé : ajouter tous les headers CORS contractuels 4. Si non autorisé : n'ajouter AUCUN header CORS 5. Preflight (OPTIONS) : répondre directement avec 204 + headers CORS 6. Journaliser les refus (événement CORS_REJECTED)

Contrat :

@Injectable()
export class CorsMiddleware implements NestMiddleware {
  constructor(
    private readonly configService: ConfigService,
    private readonly logger: Logger,
  ) {}

  use(req: Request, res: Response, next: NextFunction): void;
}


TASK-DEV-07 — GlobalRateLimitMiddleware

Fichier : src/common/middleware/global-rate-limit.middleware.ts (CRÉER)

Entrées : - PD-19-code-contracts.yaml section global_rate_limit_middleware - INV-06, INV-07, INV-08, INV-09

Comportement requis : 1. Résoudre IP client (X-Forwarded-For si proxy de confiance) 2. Appeler RateLimitService.checkGlobalLimit(ip) 3. Si allowed: true : next() 4. Si allowed: false : répondre 429 avec : - Header Retry-After: <seconds> - Corps JSON : {"error":"rate_limited","message":"Too many requests","retryAfter":<seconds>} 5. Journaliser les dépassements (événement RATE_LIMIT_EXCEEDED)

Contrat :

@Injectable()
export class GlobalRateLimitMiddleware implements NestMiddleware {
  constructor(
    private readonly rateLimitService: RateLimitService,
    private readonly configService: ConfigService,
    private readonly logger: Logger,
  ) {}

  use(req: Request, res: Response, next: NextFunction): void;
}


TASK-DEV-08 — Extension RateLimitService

Fichier : src/modules/auth/services/rate-limit.service.ts (MODIFIER)

Entrées : - PD-19-code-contracts.yaml section rate_limit_service - INV-06

Modification : Ajouter méthode checkGlobalLimit() :

private readonly KEY_PREFIX_GLOBAL = 'rl:global:';
private readonly globalMax: number;
private readonly globalWindowSeconds: number;

async checkGlobalLimit(hashedIp: string): Promise<RateLimitResult> {
  const key = this.KEY_PREFIX_GLOBAL + hashedIp;
  // INCR atomique + TTL
  // Retourne { allowed, count, limit, retryAfter?, windowResetAt? }
}

async resetGlobal(hashedIp: string): Promise<void>;
getGlobalConfiguration(): { max: number; windowSeconds: number };


TASK-DEV-09 — Modification main.ts

Fichier : src/main.ts (MODIFIER)

Entrées : - Plan §3 Phase 1-4 - ERR-05 (fail-fast)

Modifications : 1. Fail-fast validation : Ajouter validation de la configuration sécurité avant démarrage 2. Supprimer app.use(helmet()) basique 3. Supprimer app.enableCors() basique 4. Appliquer les middlewares dans l'ordre : - SecurityHeadersMiddleware (position 1) - CorsMiddleware (position 2) - GlobalRateLimitMiddleware (position 3)

// Fail-fast si config invalide
const configService = app.get(ConfigService);
validateSecurityConfig(configService);

// Middlewares de sécurité (ordre critique)
app.use(SecurityHeadersMiddleware);
// Note: CorsMiddleware et GlobalRateLimitMiddleware via AppModule.configure()

TASK-DEV-10 — AppModule middleware configuration

Fichier : src/app.module.ts (MODIFIER)

Entrées : - Plan §4 (ordre des middlewares)

Modification : Implémenter configure(consumer: MiddlewareConsumer) :

configure(consumer: MiddlewareConsumer): void {
  consumer
    .apply(SecurityHeadersMiddleware)
    .forRoutes('*')
    .apply(CorsMiddleware)
    .forRoutes('*')
    .apply(GlobalRateLimitMiddleware)
    .forRoutes('*');
}


Tâches agent-qa-unit-integration

TASK-QA-01 — Tests unitaires SecurityHeadersMiddleware

Fichier : src/common/middleware/__tests__/security-headers.middleware.spec.ts (CRÉER)

Couverture : - TC-NOM-01 : Headers de sécurité (socle) - TC-NOM-02 : Headers conditionnels (HSTS / Cache-Control) - INV-01, INV-02, INV-11

Assertions : - X-Content-Type-Options === 'nosniff' - X-Frame-Options === 'DENY' - Content-Security-Policy présent et conforme - Server et X-Powered-By absents - Cache-Control: no-store sur routes sensibles et statuts 401/403


TASK-QA-02 — Tests unitaires CorsMiddleware

Fichier : src/common/middleware/__tests__/cors.middleware.spec.ts (CRÉER)

Couverture : - TC-NOM-03 : Origine autorisée par environnement - TC-NOM-04 : Preflight CORS contractualisé - TC-ERR-01 : Origine non autorisée

Assertions : - Origine autorisée → headers CORS présents - Origine non autorisée → aucun header CORS - Preflight → 204 + Access-Control-Max-Age correct


TASK-QA-03 — Tests unitaires GlobalRateLimitMiddleware

Fichier : src/common/middleware/__tests__/global-rate-limit.middleware.spec.ts (CRÉER)

Couverture : - TC-NOM-07 : Limitation sous seuil - TC-NOM-08 : Dépassement de seuil (429) - TC-ERR-03 : Rate limiting multi-IP - TC-ERR-04 : Corps JSON contractuel

Assertions : - 100 requêtes/60s → toutes passent - 101e requête → 429 avec Retry-After et corps JSON


TASK-QA-04 — Tests E2E security-headers

Fichier : test/e2e/security-headers.e2e-spec.ts (CRÉER)

Couverture : - TC-NOM-05 : Observabilité middleware global - TC-ERR-02 : Couverture erreurs techniques (404, 405, 500) - DIV-01-v2 : Headers sur 429 et refus CORS

Scénarios : - Requête 200 → socle headers présent - Requête 404 → socle headers présent - Requête 429 → socle headers présent - Route /auth/* → Cache-Control: no-store


TASK-QA-05 — Tests E2E CORS

Fichier : test/e2e/cors.e2e-spec.ts (CRÉER)

Couverture : - TC-NOM-03, TC-NOM-04, TC-ERR-01

Scénarios : - Origine localhost:3000 en dev → CORS accepté - Origine malicious.local → CORS refusé - Preflight OPTIONS → 204 + headers complets


TASK-QA-06 — Tests E2E rate-limit-global

Fichier : test/e2e/rate-limit-global.e2e-spec.ts (CRÉER)

Couverture : - TC-NOM-07, TC-NOM-08, TC-ERR-03, TC-ERR-04

Scénarios : - Rafale 100 requêtes → OK - Rafale 101 requêtes → 429 sur la 101e - Deux IPs distinctes → isolation


TASK-QA-07 — Test d'intégration résolution IP proxy

Fichier : test/integration/rate-limit-proxy.integration-spec.ts (CRÉER)

Couverture : - DIV-02-v2 : Résolution IP via chaîne de proxy de confiance

Scénarios : - X-Forwarded-For avec proxy de confiance → IP extraite correctement - X-Forwarded-For forgé (hors trust chain) → IP source directe utilisée - Absence de X-Forwarded-For → IP source directe utilisée


TASK-QA-08 — Tests RateLimitService.checkGlobalLimit()

Fichier : src/modules/auth/services/__tests__/rate-limit.service.global.spec.ts (CRÉER)

Couverture : - Méthode checkGlobalLimit() - Compteur Redis atomique - TTL correct (60s)


Ordre d'exécution

Phase 1 (Configuration)
├── TASK-DEV-01 (security-headers.config.ts)
├── TASK-DEV-02 (cors.config.ts)
├── TASK-DEV-03 (rate-limit.config.ts)
└── TASK-DEV-04 (config.schema.ts)

Phase 2 (Middlewares)
├── TASK-DEV-05 (SecurityHeadersMiddleware)
├── TASK-DEV-06 (CorsMiddleware)
└── TASK-DEV-07 (GlobalRateLimitMiddleware)

Phase 3 (Services)
└── TASK-DEV-08 (RateLimitService.checkGlobalLimit)

Phase 4 (Bootstrap)
├── TASK-DEV-09 (main.ts)
└── TASK-DEV-10 (app.module.ts)

Phase 5 (Tests)
├── TASK-QA-01 (tests unitaires security-headers)
├── TASK-QA-02 (tests unitaires cors)
├── TASK-QA-03 (tests unitaires rate-limit global)
├── TASK-QA-04 (tests E2E security-headers)
├── TASK-QA-05 (tests E2E cors)
├── TASK-QA-06 (tests E2E rate-limit-global)
├── TASK-QA-07 (tests intégration proxy)
└── TASK-QA-08 (tests RateLimitService)

Estimation

Agent Tâches Effort estimé
agent-developer TASK-DEV-01 à TASK-DEV-10 4.5 jours
agent-qa-unit-integration TASK-QA-01 à TASK-QA-08 1.5 jours
Total 18 tâches 6 jours

Document produit par le workflow de gouvernance IA — Étape 6a Date : 2026-02-09