Files
benevoles/frontend
AdamThomasandClaude Sonnet 5 0ede409841 Limiteur de tentatives sur /auth/login et /auth/magic-link
Suite de la revue de sécurité (commit précédent) : ni /auth/login (mot
de passe COORDINATION) ni /auth/magic-link n'avaient de limite de
tentatives — aucun verrou de compte, aucun throttling, avant ouverture
au public.

`app/auth/rate_limit.py` : limiteur en mémoire (un seul processus
uvicorn, cf. Dockerfile), à créneau glissant, par clé (édition, email)
plutôt que par IP — le VPS ne transmet pas encore l'IP réelle du
client à travers Caddy, et le risque réel ici est de deviner le mot de
passe d'un compte connu (peu de comptes protégés par mot de passe),
pas une attaque distribuée sur des emails au hasard.

- `/auth/login` : 5 tentatives/minute → 429 `error.too_many_requests`.
- `/auth/magic-link` : 3 demandes/5 min (évite aussi le spam d'emails
  vers une victime ou vers le SMTP).

Fixture `_reset_rate_limiters` (autouse) pour ne pas faire fuiter l'état
d'un test à l'autre. Testé : dépassement, indépendance des clés,
expiration de la fenêtre, scénarios bout en bout sur les deux
endpoints.

Non inclus dans ce commit (édités en parallèle par l'utilisateur, laissés
en l'état) : backend/app/seed.py, frontend/tailwind.config.js.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
2026-09-16 08:19:27 +02:00
..

Frontend — Gestion des bénévoles

React + Vite + TypeScript + Tailwind. Interface en français, code en anglais.

Intègre le design Claude Design « Bénévoles Festival » et consomme l'API back-end (editions/{id}/volunteers · positions · shifts · assignments · assignments/validate · matrix).

Modes de données

VITE_API_MODE (voir .env.example) :

  • mock (défaut) — jeu de démonstration déterministe en mémoire, implémenté avec exactement la même surface que l'API (src/api/mock/). Aucun back-end requis ; utile tant que le seed serveur n'existe pas.
  • http — API réelle. VITE_EDITION_ID désigne l'édition ; en dev Vite proxifie /api vers VITE_API_TARGET (défaut http://localhost:8000).

Le passage d'un mode à l'autre ne change aucun écran : tout passe par src/api/ (client HTTP ou mock) et les hooks React Query de src/api/hooks.ts.

Authentification & rôles

Toutes les pages métier sont derrière <RequireAuth> (src/auth/).

  • Coordination → /connexion/coordination : email + mot de passe (POST /editions/{id}/auth/login).
  • Référents / bénévoles → /connexion : lien magique (POST …/auth/magic-link puis …/auth/verify). L'email étant stubé, le mode mock affiche directement le lien de démo à cliquer.
  • Le jeton JWT est stocké en localStorage, envoyé en Authorization: Bearer sur chaque requête ; une réponse 401 déconnecte.
  • Comptes de démo (mode mock) : coordination coordination@lessonsdautomne.fr / demo ; référents referent1..3@lessonsdautomne.fr ; chaque bénévole ayant un email.

Visibilité des onglets par rôle :

Onglet Coordination Référent Bénévole
01 Grille · 02 Vue bénévole · 06 Configuration ✅ — —
04 Bénévole · mobile — — ✅
05 Référent · mobile — ✅ —

L'accueil après connexion dépend du rôle (homePath).

Écrans (onglets)

Depuis l'Étape 9 back-end, une affectation n'a plus de statut : elle est valide dès sa création, la retirer = supprimer la ligne. Il n'y a donc plus d'écran « Confirmations » (ancien onglet 03).

# Route Source de données Actions
01 /grille GET /matrix (assigned / ideal) clic cellule → panneau : GET /assignments?shift_id, POST /assignments/validate par candidat, POST / DELETE /assignments
02 /benevoles volunteers + shifts + assignments lecture (surcharge / oubliés calculés côté client)
04 /mobile-benevole assignments?volunteer_id (du compte) lecture¹
05 /mobile-referent poste du référent → assignments?shift_id pointage local (hors périmètre)
06 /configuration positions · shifts · volunteers CRUD postes / créneaux (min/idéal) / bénévoles (dont suppression RGPD) ; onglet Messages → POST /notifications/planning (récapitulatif + lien de connexion)

¹ En mode http, l'API n'expose pas encore aux bénévoles le détail des créneaux : l'écran dégrade proprement (créneaux sans détail, message d'info). Fonctionne intégralement en mode mock.

Commandes

Action Commande
Installer npm install
Dev npm run dev → http://localhost:5173
Build npm run build
Lint npm run lint
Types npm run typecheck
Rendu headless + vérifs API mock npm run smoke

Conventions

  • Aucun texte d'interface en dur : src/i18n (t(key, params)) + locales/fr.json.
  • Les libellés de données (nom de poste, etc.) viennent de l'API.
  • src/api/schema.ts est le miroir de backend/app/api/schemas.py — à garder synchrone.
  • violationText() (src/lib/planning.ts) rend les violations du moteur ; les params suivent le contrat de backend/app/validation/engine.py.
  • Alias @/ → src/.

Arborescence

src/
├── main.tsx
├── api/
│   ├── schema.ts            # types miroir du back-end (dont auth)
│   ├── types.ts             # interface PlanningApi + ApiError
│   ├── session.ts           # jeton Bearer partagé + callback 401
│   ├── http-client.ts       # implémentation HTTP
│   ├── mock/                # implémentation mémoire (dataset + client + comptes + moteur)
│   ├── index.ts             # sélection mock / http, EDITION_ID
│   └── hooks.ts             # hooks React Query (lectures + mutations)
├── auth/           auth-context (AuthProvider/useAuth) · RequireAuth · roles
├── app/            AppShell (onglets filtrés par rôle) · router
├── pages/          un fichier par écran (+ auth/, grille/, config/, mobile/)
├── components/     ui.tsx (primitives) · state.tsx (Loading / ErrorNote)
├── lib/            time.ts · planning.ts (treillis, heures, surcharge, violationText) · env.ts · cx.ts
└── i18n/           t() + locales/fr.json