# Sécurité — AMA Buffet

Document vivant. Il décrit ce qui est déjà en place, ce qui reste à faire, et les
règles de développement à suivre. À relire à chaque sprint.

## 1. Modèle de menaces (résumé)

| Actif | Menace | Contre-mesure en place |
| --- | --- | --- |
| Comptes clients | Bourrage d'identifiants, énumération de comptes | Argon2id, limitation de débit, blocage temporaire après 5 échecs, réponse identique à l'inscription que l'e-mail existe ou non |
| Session | Vol de cookie, XSS, CSRF | Cookie `httpOnly` + `Secure` + `SameSite=Strict`, CSP sans `unsafe-inline`, jeton CSRF en double soumission |
| Commandes | Manipulation des prix, paniers fantômes | Montants recalculés côté serveur, remise validée serveur, verrou `SELECT … FOR UPDATE` sur le stock de paniers |
| Espace gestion | Élévation de privilèges | Rôles client/staff/manager/owner, contrôle sur chaque route, transitions de statut whitelistées |
| Données personnelles | Fuite, conservation abusive | IP jamais stockée en clair (hachée), export et suppression en self-service, consentement marketing horodaté et distinct |
| Notifications | Envois non consentis, spam | Campagnes limitées aux comptes `marketing_opt_in`, blocage 21 h – 8 h, abonnements périmés purgés ; les notifications de suivi de commande restent transactionnelles |
| Paiement | Commande déclarée payée par le client, rejeu, montant falsifié | Statut `paid` posé uniquement par le webhook à signature vérifiée, montant comparé au total serveur, traitement idempotent, clé d'idempotence à la création |
| Infrastructure | Conteneur compromis | Processus non-root, `cap_drop: ALL`, `no-new-privileges`, système de fichiers en lecture seule, base non exposée hors du réseau interne |

## 2. Règles de développement

1. **Aucun secret dans le code ni dans l'image.** Tout passe par variables d'environnement ; `.env` n'est jamais versionné. En production, préférer les secrets du gestionnaire de l'hébergeur.
2. **Toute entrée est validée par un schéma Pydantic** avec longueurs bornées. Pas de `dict` libre venant du client.
3. **Aucune requête SQL construite par concaténation.** SQLAlchemy uniquement, paramètres liés.
4. **Aucun script en ligne dans le HTML.** La CSP interdit `unsafe-inline` ; tout le JS est dans des fichiers servis depuis la même origine.
5. **Échapper systématiquement** ce qui est injecté dans le DOM (`esc()` côté front).
6. **Les erreurs serveur ne fuitent jamais de détail technique** au client ; la pile va dans les journaux.
7. **Chaque action sensible est journalisée** dans `audit_log` (auteur, action, cible, IP hachée).
8. **Dépendances épinglées** et scannées à chaque CI (`pip-audit`, `bandit`, `trivy`).
9. **Revue obligatoire** avant fusion sur `main` ; la CI doit être verte.
10. **Le client ne décide jamais d'un montant, d'un rôle ou d'un statut.** Il propose, le serveur tranche.

## 3. Conformité France / UE

- **RGPD** : export (`GET /api/auth/export`) et suppression (`DELETE /api/auth/me`) en self-service. La suppression anonymise le compte mais conserve les ventes, obligation comptable.
- **Consentement marketing** distinct du fonctionnel, horodaté, révocable en un geste.
- **Allergènes** : le champ est prévu et affiché quand il est rempli, mais il est
  **vide dans la carte chargée** : à compléter avec la cuisine avant l'ouverture
  au public, l'information devant être accessible avant l'achat.
- **Bilinguisme** : le français reste la langue de référence (mentions légales,
  CGV, allergènes) ; l'anglais est une traduction de service. En cas d'écart,
  c'est la version française qui fait foi — à préciser dans les CGV. La langue
  est une préférence d'affichage, jamais un paramètre de sécurité : elle ne
  change ni les droits, ni les montants, ni les règles de validation.
- **Prix TTC** en euros, total final affiché avant paiement.
- **Paiement** : aucune donnée de carte ne transite par le serveur du restaurant (Stripe hébergé, 3-D Secure).
- Restent à rédiger avec le client : mentions légales, CGV (annulation, non-retrait), politique de confidentialité, registre des traitements.

## 4. Limites connues de la version actuelle

Ces points sont assumés pour la phase de test, à traiter avant l'ouverture au public :

1. **Limitation de débit en mémoire** — repasser sur Redis dès qu'il y a plus d'une réplique de l'API.
2. **Envoi d'e-mails non chiffré de bout en bout** : dépend du serveur SMTP configuré.
3. **Sauvegardes non planifiées par défaut** : le script existe, la planification
   et le test de restauration relèvent de l'exploitation.
4. **Sauvegardes** : `make backup` chiffre avec `age` quand la clé publique est
   renseignée. Sans elle, le dump contient des données personnelles en clair et
   le script le dit explicitement. La clé privée ne doit jamais être sur le serveur.
5. **Journalisation** locale uniquement — prévoir une collecte centralisée et une rétention définie.

## 5. Avant la mise en production

- [ ] Secrets régénérés (`openssl rand -base64 48`), différents de ceux du test
- [ ] `AMA_ENV=prod` : la documentation d'API (`/docs`) se désactive automatiquement
- [ ] `AMA_COOKIE_SECURE=true` et HTTPS obligatoire
- [ ] Compte propriétaire créé, puis `AMA_SEED_ADMIN_*` retiré du `.env`
- [ ] Sauvegarde de base testée en restauration
- [x] Migrations Alembic en place (`alembic upgrade head` à chaque démarrage)
- [ ] Stripe en mode production : clés live, webhook `payment_intent.succeeded` et `payment_intent.payment_failed` déclaré sur `https://<domaine>/api/payments/webhook`
- [ ] Vérifier que le webhook est bien reçu (journal Stripe) avant d'ouvrir au public
- [ ] Textes légaux publiés
- [x] Prix réels du restaurant saisis (carte de septembre 2026)
- [ ] Allergènes renseignés pour chaque plat
- [ ] Zones de livraison validées avec le restaurant (rayons, frais, minimums)
- [ ] Contrat coursier signé (Stuart ou Uber Direct) et intégration API activée
- [ ] Politique de non-retrait tranchée et écrite dans les CGV
- [ ] Prix et créneau du panier anti-gaspi validés (le panier est créé non publié)
- [ ] Test d'intrusion de l'application avant ouverture au public


## 6. Comptes, jetons et contenus téléversés

- **Jetons de vérification et de réinitialisation** : seul le condensat SHA-256
  est stocké, l'usage est unique, la durée de vie courte (48 h / 30 min). Une
  réinitialisation incrémente `token_version`, ce qui coupe toutes les sessions
  ouvertes ailleurs — c'est le point important si un compte a été compromis.
- **Mot de passe oublié** : réponse identique que l'adresse existe ou non, pour
  ne pas transformer le formulaire en détecteur de comptes.
- **Invitation d'un membre** : aucun mot de passe n'est choisi par un tiers ; la
  personne définit le sien via un lien. Un changement de rôle invalide aussi les
  sessions, pour que des droits retirés le soient immédiatement.
- **Photos** : le type est déterminé par les octets d'en-tête du fichier, jamais
  par son nom ni par le Content-Type annoncé. Le fichier est réécrit sous un nom
  généré, dans un volume séparé, servi en lecture seule.
- **Non-retrait** : aucun remboursement automatique. Une règle qui rendrait
  l'argent sans décision humaine serait exploitable (commander, ne pas venir,
  être remboursé) et coûteuse, puisque le plat est produit.
- **Remboursements** : déclenchés côté serveur uniquement, avec clé
  d'idempotence liée à la commande et au montant, donc insensibles au double
  clic. Un paiement sur place ne peut pas être remboursé : il n'a jamais été
  encaissé. Chaque remboursement est journalisé avec son auteur.
- **Audience des campagnes** : la requête part toujours des comptes ayant
  consenti ; la sélection manuelle ne peut que restreindre cette base, jamais
  l'élargir. Un opt-out reste donc respecté même par erreur de manipulation.
- **Liste clients pour campagne** : réservée au gérant, non exportable depuis
  cet écran, et limitée à ce qui sert au ciblage.
- **Connexion par téléphone** : numéro unique et normalisé, mêmes limitations de
  tentatives et même réponse indifférenciée qu'avec l'e-mail.
- **Exports** : réservés au propriétaire. Ils contiennent des données de vente,
  pas d'adresse ni d'e-mail client — un fichier qui traîne sur un poste ne doit
  pas devenir une fuite de fichier clients.
- **Carte bancaire** : le champ est une iframe de Stripe ; la CSP n'autorise que
  `js.stripe.com` et `api.stripe.com`. Aucun numéro ne touche notre page ni nos
  journaux.

## 7. Livraison et paiement sur place

- **Le client ne décide ni de sa zone ni de ses frais** : le devis affiché est
  indicatif, et le tarif est recalculé au moment de la commande à partir de la
  position géocodée. Un tarif modifié entre-temps s'applique immédiatement.
- **La distance est à vol d'oiseau** (formule de haversine). C'est une borne
  basse du trajet réel : les rayons de zone doivent être choisis en conséquence,
  un rayon de 3 km couvrant environ 4 km de trajet en ville.
- **Le géocodage passe par le serveur**, jamais depuis le navigateur : la CSP
  n'autorise que notre origine, et l'adresse du client ne part pas vers un tiers
  depuis son appareil. Le service utilisé est la Base Adresse Nationale
  (service public français), sans clé ni compte.
- **Paiement sur place** : la commande passe directement au statut `paid` pour
  entrer en cuisine, alors que l'encaissement n'a pas eu lieu. Deux garde-fous :
  un plafond de montant, et l'interdiction pour les paniers anti-gaspi. Le
  risque résiduel est le désistement — à suivre via le rapport « non retirées ».
- **Coupons** : chaque utilisation est enregistrée (`coupon_redemptions`), ce qui
  permet le quota par client et l'audit des remises accordées.
- **Données de livraison** : adresse, téléphone et coordonnées sont figés sur la
  commande. Ce sont des données personnelles — elles partent dans l'export RGPD
  du client et disparaissent avec la suppression de compte.

## 8. Chaîne de paiement (Stripe)

1. Le client valide son panier → `POST /api/orders` crée la commande au statut
   `pending_payment` et réserve le stock de paniers anti-gaspi.
2. `POST /api/payments/intent/{id}` renvoie un `client_secret`. Le formulaire de
   carte est hébergé par Stripe : aucun numéro ne transite par notre serveur.
3. Stripe appelle `POST /api/payments/webhook`. La signature est vérifiée, le
   montant est comparé au total calculé côté serveur, puis la commande passe à
   `paid` et une notification part vers le client.
4. En cas d'échec ou d'abandon, le stock de paniers est relibéré.

Le retour du navigateur n'est jamais une preuve de paiement. La route de
confirmation manuelle n'existe qu'en mode `fake`, pour les tests.

### Tester le webhook en local

```bash
stripe listen --forward-to localhost:8080/api/payments/webhook
stripe trigger payment_intent.succeeded
```
