Skip to main content

Rate limiting

THE HIVE applique un rate limiting par IP sur les endpoints sensibles (authentification, inscription, reset password, upload). L’IP client est récupérée depuis CF-Connecting-IP (Cloudflare) puis X-Forwarded-For puis l’IP de la connexion.

Seuils par catégorie

CatégorieFenêtreLimiteEndpoints concernés
Auth critique1 min5 reqPOST /auth/login, POST /auth/forgot-password, POST /auth/reset-password
Auth standard1 min10 reqPOST /auth/refresh-token, POST /auth/logout, GET /auth/validate-email
Inscription1 h3 reqPOST /candidates/register, POST /recruiters/register
Upload10 min20 reqPOST /files/*, /candidats/me/cv, /candidats/me/photo
Candidature1 h30 reqPOST /candidatures
Search1 min60 reqGET /offres/search, /offres/autocomplete
Standard1 min120 reqTous les autres

Décision

Headers de réponse

Toute requête limitée renvoie :
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1713454200
Retry-After: 47
Content-Type: application/json

{
  "timestamp": "2026-04-18T14:00:00Z",
  "status": 429,
  "error": "Too Many Requests",
  "message": "Trop de tentatives. Réessayez dans 47 secondes.",
  "path": "/v1/api/auth/login"
}

Algorithme

Token bucket Redis — un compteur par (catégorie, IP). Reset glissant avec TTL équivalent à la fenêtre.

Contournement impossible

Les tentatives de contournement (rotation User-Agent, rotation cookies) ne fonctionnent pas : la clé Redis est indexée sur l’IP, pas sur des métadonnées client.

Bannissement IP

Une IP qui déclenche plus de 50 réponses 429 en 1 heure est ajoutée à Fail2ban et bloquée au niveau UFW pour 24h.

Exceptions

  • Les IPs internes Bomunto sont en whitelist.
  • Les IPs Cloudflare ne sont jamais bannies (Cloudflare agit comme proxy légitime).

Tester le rate limit en local

En dev, le rate limiting est désactivé par défaut. Pour le tester :
./mvnw spring-boot:run -Dspring-boot.run.profiles=dev,rate-limit-test

Bonnes pratiques client

  • Respecter Retry-After.
  • Implémenter un exponential backoff : 1s, 2s, 4s, 8s, 16s max.
  • Utiliser le refresh token plutôt que relogin à chaque session.
  • Pour des imports en masse (ex. ajout vivier), batcher côté serveur via un endpoint dédié plutôt que boucler.
async function fetchWithBackoff(url, opts, maxRetries = 5) {
  for (let i = 0; i <= maxRetries; i++) {
    const res = await fetch(url, opts);
    if (res.status !== 429) return res;
    const retryAfter = parseInt(res.headers.get("Retry-After") || "1", 10);
    const wait = Math.min(retryAfter * 1000, 2 ** i * 1000);
    await new Promise(r => setTimeout(r, wait));
  }
  throw new Error("Rate limit persistant");
}