diff --git a/.env.example b/.env.example index 1f6a36f..9cb66b3 100644 --- a/.env.example +++ b/.env.example @@ -72,3 +72,29 @@ FACEBOOK_PAGE_ACCESS_TOKEN= LINKEDIN_ORGANIZATION_URN= LINKEDIN_ORGANIZATION_ID= LINKEDIN_ACCESS_TOKEN= + +# ---- Boîte mail de l'association ---- +# La configuration se fait dans Backend › Boîte mail : on y branche Google +# (API Gmail), Microsoft (API Graph) ou un serveur SMTP, et les secrets sont +# stockés chiffrés. Rien à mettre ici dans le cas normal. +# +# Les jetons et mots de passe utilisent la même clé que les réseaux sociaux +# (SOCIAL_TOKEN_KEY, avec repli sur AUTH_SECRET). +# +# `off` arrête la file d'envoi sans toucher au reste (le libérateur de +# publications programmées a son propre interrupteur, PROMO_SCHEDULER). +#MAIL_WORKER=off +# Débit de la file, en messages par passage (défaut : 20). Gmail plafonne +# autour de 500 destinataires par jour sur un compte gratuit. +#MAIL_RATE_PER_MINUTE=20 + +# --- Repli avant toute configuration depuis le backoffice --- +# Permet un premier déploiement sans passer par l'écran. Dès qu'un compte est +# enregistré dans Backend › Boîte mail, il prime sur ces variables. +SMTP_HOST= +SMTP_PORT=465 +SMTP_SECURE=true +SMTP_USER= +SMTP_PASSWORD= +SMTP_FROM= +SMTP_FROM_NAME= diff --git a/CLAUDE.md b/CLAUDE.md index 638e0e8..f213cf9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -202,7 +202,11 @@ même raison. - Sessions JWT limitées à 7 jours (`auth.config.ts`), HSTS et suppression de `X-Powered-By` dans `next.config.mjs`. Le port Postgres de `docker-compose` n'est publié que sur `127.0.0.1`. -- `npm test` verrouille ces protections (`tests/security.test.ts`). +- `npm test` verrouille ces protections (`tests/security.test.ts`) : XSS du + journal, limitation des connexions, chiffrement des secrets, coordonnées du + référent, **mots de passe temporaires jamais mis en file**, secrets de + messagerie jamais renvoyés au navigateur, aucune copie cachée dans la chaîne + d'envoi, et rien rendu en HTML brut côté informations. ## Référencement (SEO) @@ -274,6 +278,96 @@ même raison. `admin` > `moderator` > `editor` are staff; `member` is an adhérent linked to a `members` row via `users.memberId`. Capability matrix is in `src/lib/rbac.ts`. +`manageInformations` (admin + modérateur) ouvre la rédaction des informations ; +écrire à tous les adhérents reste sous `manageEmails` (admin seul), et la +configuration de la boîte mail sous `manageSettings`. Un modérateur publie donc +une information sans pouvoir la diffuser. + +## Informations adhérents + +Le fil de l'espace adhérent (`/backend/espace/informations`, onglet en tête) +porte ce que publie le bureau depuis `/backend/informations`. Deux tables : +`informations` (brouillon → publiée, `pinned`, `email_sent_at`) et +`information_reads`. + +- **Texte riche** : `src/lib/rich-text.ts` est **pur** (`tests/rich-text.test.ts`) + et analyse un sous-ensemble de Markdown — `**gras**`, `*italique*`, `- puce`, + `1. numéro`, `[texte](https://…)`, `## sous-titre`. Deux rendus, un seul + analyseur : `richTextNodes()` pour l'écran (des éléments React, jamais + `dangerouslySetInnerHTML`), `richTextToEmailHtml()` pour le message. L'aperçu + du formulaire passe par le premier, il est donc fidèle par construction. Un + lien hors `http`/`https` perd sa cible et ne garde que son libellé + (`safeHttpUrl`, partagé avec `email-templates.ts`). +- **Pas de WYSIWYG** : il produirait du HTML, qu'il faudrait stocker puis + assainir — seconde dépendance, seconde surface d'attaque. La saisie est un + `