ADR-05: Anti-spam
Stratégie multi-couche contre l'abus de signalement
Contexte
Le scénario d'abus principal : quelqu'un prend en photo le QR code et spamme des signalements depuis chez lui. Chaque faux signalement peut déclencher un WhatsApp (~0.05€) et polluer le dashboard. Il faut bloquer sans gêner les utilisateurs légitimes.
Décision
Défense en profondeur — 5 couches complémentaires.
Couche 1 : Token rotatif (passif)
Le token dans l'URL du QR code expire après 7 jours. Un photo prise aujourd'hui ne fonctionnera plus dans une semaine.
| Scénario | Résultat |
|---|---|
| Scan sur place (token valide) | ✅ Accès autorisé |
| Photo prise il y a 2 jours | ✅ Encore valide |
| Photo prise il y a 8 jours | ❌ Token expiré → message d'erreur |
Couche 2 : Rate limiting par IP
Limiter le nombre de signalements par adresse IP sur une fenêtre glissante.
| Limite | Fenêtre | Action |
|---|---|---|
| 3 signalements | 15 minutes | Blocage temporaire |
| 10 signalements | 1 heure | Blocage 1h |
| 20 signalements | 24 heures | Blocage 24h + alerte admin |
L'IP est hashée (SHA-256 + salt) en base — pas de stockage d'IP en clair (RGPD).
Couche 3 : Device fingerprinting
Un fingerprint du device (combinaison User-Agent + résolution + timezone + langue) est généré côté client et hashé. Même limites que l'IP mais par device.
const fingerprint = await generateFingerprint({
userAgent: navigator.userAgent,
screen: `${screen.width}x${screen.height}`,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
language: navigator.language,
});Couche 4 : Cooldown par device
Après un signalement réussi, le même device ne peut pas signaler le même WC pendant 10 minutes. Stocké dans un cookie court (httpOnly) + vérifié côté serveur.
Couche 5 : Détection de patterns anormaux
Analyse côté serveur des patterns suspects :
| Pattern | Seuil | Action |
|---|---|---|
| Même fingerprint, WC différents | 5 WC en 10 min | Blocage + flag |
| Même IP, même texte libre | 2 identiques | Rejet du duplicata |
| Volume anormal sur un WC | 10 signalements/heure | Alerte admin + auto-ignore |
Couches NON retenues
| Couche | Raison du rejet |
|---|---|
| CAPTCHA (reCAPTCHA, hCaptcha) | Friction trop forte pour un parcours de 10 secondes |
| Géolocalisation GPS | Nécessite la permission explicite, taux de refus élevé |
| Inscription obligatoire | Contraire au principe d'anonymat et de rapidité |
| Vérification par SMS | Coût, friction, nécessite un numéro de téléphone |
Conséquences
- Positif : 5 couches complémentaires, difficile de contourner toutes
- Positif : Aucune friction pour l'utilisateur légitime (tout est invisible)
- Positif : Pas de données personnelles stockées (IP hashée, fingerprint hashé)
- Attention : Le fingerprinting n'est pas parfait (navigation privée, VPN)
- Attention : Les faux positifs sont possibles (Wi-Fi partagé → même IP)
- Attention : L'agrégation WhatsApp (ADR-03) sert aussi d'anti-coût passif