Commit Graph
5 Commits
Author SHA1 Message Date
AdamThomasandClaude Sonnet 5 89e74e5ec7 Revue de sécurité avant ouverture au public
Audit de l'application avant sa première publication sur internet.
Correctifs immédiats, peu coûteux et sans changement de comportement
en dev :

- Le backend refuse de démarrer en `ENVIRONMENT=production` si
  `JWT_SECRET` est vide, trop court ou égal à la valeur par défaut
  versionnée dans `.env.example` — sinon n'importe qui pourrait forger
  un jeton de session valide (y compris pour un compte COORDINATION)
  sans jamais s'authentifier. Testé (`test_config.py`).
- Avertissement au démarrage si `EMAIL_ENABLED=false` en production :
  dans ce cas le jeton de lien magique est journalisé en clair (confort
  de dev, cf. `api/auth.py`) — un identifiant de connexion en clair dans
  les logs du conteneur en public serait un vrai problème.
- `frontend/nginx.conf` : plus de journal d'accès (la route
  `/connexion?token=…` y ferait apparaître le jeton de lien magique en
  clair), en-têtes `X-Content-Type-Options`, `X-Frame-Options`,
  `Referrer-Policy` (empêche aussi la fuite de ce même jeton vers
  Google Fonts via l'en-tête Referer), `server_tokens off`.
- `deploy/Caddyfile.benevoles` : mêmes en-têtes côté reverse proxy
  public, plus `Strict-Transport-Security`.
- `deploy/README.md` : check-list sécurité avant ouverture (secret,
  SMTP, ne jamais mettre en production la base de démo `app.seed` — le
  mot de passe du compte coordination y est versionné en clair —,
  limites connues comme l'absence de rate-limiting sur `/auth/*`).

Points vérifiés sans modification nécessaire : autorisation par rôle et
cloisonnement par édition cohérents sur tous les endpoints (`owned_or_404`
+ vérification d'édition partout), mots de passe bcryptés, jetons de
lien magique à usage unique haché en base (256 bits d'entropie), pas de
PII/jeton dans les logs applicatifs (RGPD déjà en place), pas
d'injection de gabarit dans les emails (substitution sur liste blanche,
pas de `.format()`), pas d'injection d'en-tête SMTP (`EmailMessage` du
socle Python), pas de `dangerouslySetInnerHTML` côté front, jeton JWT
en `Authorization: Bearer` (pas de cookie ⇒ pas de CSRF), `.env` jamais
versionné, dépendances à jour.

Restent des choix à trancher avec l'utilisateur plutôt qu'à décider
seul (compromis effort/risque) : rate-limiting sur les endpoints
d'authentification, désactivation de `/docs` en production, conteneurs
non-root.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
2026-09-16 08:14:00 +02:00
AdamThomasandClaude Sonnet 5 b84f62dfab Emails : robustesse SMTP + diagnostic
- config.py : retrait des identifiants OVH réels remis en dur ; défauts
  neutres (email_enabled=false). Les vraies valeurs vont dans .env.
- open_smtp : le port 465 force le SSL implicite (SMTPS) même si SMTP_SSL=false
  — c'était la cause des envois qui ne partaient pas (connexion en clair sur un
  port TLS -> timeout).
- deliver_many : les erreurs de connexion / auth / rejet sont journalisées
  avec le message du serveur et ne remontent plus (l'appel HTTP n'échoue pas).
- Lien magique : envoi en tâche de fond (BackgroundTasks) comme les
  notifications ; ne bloque plus la réponse.
- app/email/check.py : `python -m app.email.check <destinataire>` — affiche la
  config effective et envoie un email de test, avec la cause exacte en cas
  d'échec.
- Tests : fixture autouse forçant EMAIL_ENABLED=false (aucun test ne touche un
  vrai SMTP, quel que soit le .env local) ; test_full_flow lit l'email capturé
  au lieu des logs. 137 tests OK.
- .env.example : notes port 465 / From = boîte authentifiée / commande check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
2026-09-10 19:31:02 +02:00
AdamThomasandClaude Sonnet 5 b268e308ab Étape 10 : envoi d'emails par SMTP
- app/email/ : sender (smtplib, STARTTLS ou SSL, connexion réutilisée pour un
  lot) + templates (rendu, textes dans locales/fr.json). EMAIL_ENABLED=false
  (défaut) ⇒ journalisé seulement, sans destinataire ni corps (RGPD).
- Lien magique : POST .../auth/magic-link envoie désormais l'email
  (PUBLIC_BASE_URL + magic_link_path + ?token=). Helper app/auth/magic_link.py
  partagé. Sans SMTP, le jeton non nominatif reste journalisé pour le dev.
- Récapitulatif de planning : POST /editions/{id}/notifications/planning
  (COORDINATION). subject/body sont des modèles ({prenom}, {nom}, {nb_creneaux},
  {nb_heures}, {creneaux}, {lien_connexion} → lien magique personnel émis).
  Cible tous les bénévoles avec email ou une liste. Envoi en BackgroundTasks.
- Config : PUBLIC_BASE_URL, EMAIL_ENABLED, SMTP_* ; .env.example,
  docker-compose (dev + prod), deploy/README.
- 9 tests (sender activé/désactivé, rendu, notifications : contenu, gabarit par
  défaut, lien magique embarqué, sous-ensemble, réservé COORDINATION).
  137 tests au total ; ruff + alembic check verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
2026-09-10 08:08:55 +02:00
AdamThomasandClaude Sonnet 5 ced922f785 Étape 7 : authentification par lien magique + JWT + rôles
- app/auth/ : jetons (lien magique aléatoire haché SHA-256, JWT HS256),
  dépendances get_current_account / current_account_in_edition / require_roles
  + require_coordinator / require_staff, et ensure_can_write_position/_shift
- Endpoints app/api/auth.py : POST .../auth/magic-link (202 constant, pas de
  divulgation), POST .../auth/verify (JWT, jeton à usage unique), GET /auth/me
- Envoi d'email stubé : le lien est journalisé (logger app.auth, identifiants
  seulement, aucune donnée nominative)
- app/api/accounts.py : CRUD des comptes réservé à la COORDINATION
- Rôles appliqués sur toute l'API :
  COORDINATION accès complet ; RÉFÉRENT lecture globale + écriture sur ses
  seuls postes/créneaux/affectations ; BÉNÉVOLE sa fiche et ses affectations,
  aucune écriture
- imports.py aligné sur la dépendance get_edition (404 avant 403)
- Secret JWT porté à >= 32 octets (config, .env.example, docker-compose)
- seed : crée un compte VOLUNTEER pour chaque bénévole avec email (104 comptes)
- conftest : fixtures make_account / coordinator / as_account / raw_client ;
  la suite existante s'exécute en COORDINATION
- 13 tests d'authentification + de rôles ; 119 tests au total ; alembic check
  toujours vert (Account / MagicLinkToken déjà dans la migration 0001)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
2026-09-09 23:22:35 +02:00
AdamThomasandClaude Sonnet 5 09d7d70076 Étape 1 : squelette du projet
- Monorepo backend/ + frontend/ + deploy/
- Backend FastAPI/SQLModel/Alembic/uv qui démarre (health check, config,
  i18n, base de test PostgreSQL, pytest)
- docker-compose de développement (db + backend hot-reload)
- Chaîne CI/CD GitHub Actions : lint + tests, build image, push GHCR,
  déploiement SSH sur le VPS à chaque push main
- CLAUDE.md : vocabulaire du domaine, conventions, commandes

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