> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wethehivers.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limiting

> Seuils, fenêtres, comportement et headers de rate limiting

# 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

```mermaid theme={null}
flowchart TD
    REQ[Requête] --> IP{Extraire IP}
    IP --> CF{CF-Connecting-IP?}
    CF -->|oui| USE_CF[Utiliser CF]
    CF -->|non| XFF{X-Forwarded-For?}
    XFF -->|oui| USE_XFF[Utiliser 1er IP de la chaîne]
    XFF -->|non| USE_CONN[IP de la connexion TCP]

    USE_CF --> CAT[Catégoriser endpoint]
    USE_XFF --> CAT
    USE_CONN --> CAT

    CAT --> BUCKET[Token bucket Redis\n clé: rl:&lt;cat&gt;:&lt;ip&gt;]
    BUCKET --> CHECK{Jetons dispo?}
    CHECK -->|oui| DEC[Décrémenter\n puis continuer]
    CHECK -->|non| R429[429 Too Many Requests\n Retry-After: &lt;s&gt;]
```

## Headers de réponse

Toute requête limitée renvoie :

```http theme={null}
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.

```mermaid theme={null}
sequenceDiagram
    participant C as Client
    participant N as Nginx
    participant B as Backend
    participant R as Redis

    C->>N: POST /v1/api/auth/login
    N->>B: Forward + CF-Connecting-IP
    B->>R: INCR rl:auth_crit:1.2.3.4
    R-->>B: 6
    B->>R: EXPIRE rl:auth_crit:1.2.3.4 60
    B->>B: 6 > 5 → reject
    B-->>C: 429 Too Many Requests
```

## Contournement impossible

<Warning>
  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.
</Warning>

## 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.

```mermaid theme={null}
flowchart LR
    C[Client abusif] -->|>50 x 429| F2B[Fail2ban jail]
    F2B --> UFW[UFW DROP 24h]
    UFW --> BLOCK[Paquets IP bloqués\n au niveau kernel]
```

## 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 :

```bash theme={null}
./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.

```javascript theme={null}
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");
}
```
