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égorie | Fenêtre | Limite | Endpoints concernés |
|---|
| Auth critique | 1 min | 5 req | POST /auth/login, POST /auth/forgot-password, POST /auth/reset-password |
| Auth standard | 1 min | 10 req | POST /auth/refresh-token, POST /auth/logout, GET /auth/validate-email |
| Inscription | 1 h | 3 req | POST /candidates/register, POST /recruiters/register |
| Upload | 10 min | 20 req | POST /files/*, /candidats/me/cv, /candidats/me/photo |
| Candidature | 1 h | 30 req | POST /candidatures |
| Search | 1 min | 60 req | GET /offres/search, /offres/autocomplete |
| Standard | 1 min | 120 req | Tous les autres |
Décision
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");
}