CLAUDE.md gagne les deux sections et, surtout, les pièges qui coûteraient
une soirée à retrouver : `db.execute` qui rend les colonnes brutes, les
deux interrupteurs de boucle qu'il ne faut pas confondre, la rotation du
jeton Microsoft, l'adresse d'expédition lue chez le fournisseur, et la
raison pour laquelle les mots de passe temporaires ne passent jamais par
la file.
README et .env.example décrivent les trois transports, le piège des sept
jours côté Google, et le repli SMTP par variables d'environnement qui
permet un premier déploiement sans démarche préalable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QsLjRAnuLwivqxCbM4WeP
Le Caddyfile gagne un bloc fourre-tout http:// à côté du bloc du nom de
domaine. L'installation complète, Caddy compris, devient donc utilisable
immédiatement sur http://IP_DU_SERVEUR, et bascule seule en HTTPS dès que le
DNS pointe sur la machine : Caddy réessaie la demande de certificat en
arrière-plan, sans intervention ni redémarrage.
Le préfixe http:// désactive le HTTPS automatique pour ce seul bloc, aucune
autorité ne délivrant de certificat pour une IP nue. Le bloc du domaine reste
prioritaire et sa redirection HTTP vers HTTPS est insérée avant le fourre-tout.
Vérifié avec le binaire Caddy 2.8.4 : configuration valide, et à l'exécution un
308 vers https:// pour l'en-tête Host du domaine contre un passage au proxy
pour l'accès par IP.
Rend inutile la surcouche docker-compose.http.yml, retirée, la surcouche Caddy
couvrant désormais les deux situations. README et .env.example décrivent ce
parcours unique.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8UUiQtBWYWNFcsea8spnX
Ajoute docker-compose.http.yml, qui publie l'application sur le port 80 et
laisse Compose créer le réseau proxy. Permet de valider une installation sur
l'IP du serveur avant que le DNS ne soit en place, sans HTTPS possible à ce
stade (pas de certificat pour une IP nue).
Documente les deux phases dans le README et .env.example : HTTP simple d'abord,
puis bascule vers la surcouche Caddy en changeant la seule ligne COMPOSE_FILE,
les volumes conservant base, compte admin et secret de session.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8UUiQtBWYWNFcsea8spnX
Ajoute docker-compose.caddy.yml et un Caddyfile pour servir le site sur un
nom de domaine en 80/443 avec certificat automatique, sans Nginx Proxy
Manager. Le port 8413 n'est plus publié dans ce mode et le réseau proxy est
créé par Compose. Procédure documentée dans le README et .env.example.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B8UUiQtBWYWNFcsea8spnX
Le seed ne crée plus que le référentiel des catégories et le compte
administrateur initial. Les adhérents, promotions, demandes d'adhésion,
entrées du journal et comptes de démonstration sont déplacés dans
src/db/demo-data.ts et ne sont insérés qu'avec SEED_DEMO=true.
Nouveau script `npm run db:purge-demo` (bundlé en dist/purge-demo.cjs pour le
conteneur) : retire exactement ce jeu de démonstration d'une base qui l'a reçu
lors des démarrages précédents, sans toucher aux saisies de l'association ni
aux catégories. Idempotent, en transaction ; un compte réel rattaché à une
fiche de démo est détaché et non supprimé.
Vérifié en local sur Postgres 16 : seed par défaut = 59 catégories + 1 admin
et rien d'autre ; seed avec SEED_DEMO=true = jeu complet ; purge = retour à
l'état vide ; seconde purge sans effet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAQsCp4nnZbwexCg7NDBHE
Audit de sécurité complet de l'application, puis correctifs du lot A validés :
- Seed : plus aucun compte de démonstration (mot de passe « changeme123 »)
sans SEED_DEMO=true. L'administrateur initial reçoit un mot de passe
aléatoire affiché une fois dans les journaux (ou SEED_ADMIN_PASSWORD) et
doit le changer à la première connexion. docker-compose ne fournit plus de
mot de passe par défaut.
- Mots de passe temporaires : la colonne users.temp_password (en clair) est
supprimée (migration 0011). Création d'adhérent, réinitialisation,
rattrapage des comptes manquants, approbation de demande et invitation
staff renvoient les identifiants, affichés une seule fois par le composant
OneTimeCredentials, sans redirection. L'invitation staff, qui ne
communiquait jamais le mot de passe, redevient utilisable et impose le
changement à la première connexion.
- Sessions JWT limitées à 7 jours (30 auparavant).
- En-têtes : Strict-Transport-Security ajouté, X-Powered-By supprimé.
- docker-compose : port Postgres publié sur 127.0.0.1 uniquement.
- Image Docker sur node:22 (Node 20 en fin de vie).
- Dépendances : next 15.5.25, pg 8.23, nanoid 3.3.18 (avis GHSA-2v37-7h3g-55p8).
Vérifié en local : tests, typage, build de production, migration + seed sur
un Postgres 16, et parcours navigateur complet (première connexion, changement
forcé, création / réinitialisation / invitation avec affichage unique, cookie
de session à 7 jours).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RAQsCp4nnZbwexCg7NDBHE
Connecter un réseau social imposait de poser NEXT_PUBLIC_SITE_URL dans
docker-compose et de redémarrer le conteneur. L'écran Réseaux sociaux porte
désormais le champ, pré-rempli avec l'adresse par laquelle l'administrateur
consulte la page.
- Nouveau réglage `site_public_url` ; `siteUrl()` lit la base puis retombe sur
les variables d'environnement, donc les déploiements actuels ne bougent pas.
- `siteUrl()`, `redirectUri()`, `authorizeUrl()`, `promoLink()` et
`buildPromoMessage()` deviennent asynchrones.
- Seule l'origine est conservée : le schéma est ajouté s'il manque, un chemin
est retiré, et une adresse invalide revient sur l'écran avec un message au
lieu d'une page d'erreur brute.
- `saveSiteSettings` saute cette clé : le formulaire Paramètres ne la contient
pas et l'aurait écrasée à chaque enregistrement.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
Les jetons se posaient à la main dans docker-compose. Un écran Backend ›
Réseaux sociaux permet désormais de coller les identifiants de l'application,
cliquer Connecter, choisir la page, et ne plus y revenir.
- Table `social_accounts` : identifiants, jetons et cible par réseau. Tous les
secrets sont chiffrés en AES-256-GCM (`src/lib/crypto.ts`), clé
`SOCIAL_TOKEN_KEY` avec repli sur `AUTH_SECRET`.
- Routes `api/social/[network]/{connect,callback}` : `state` anti-CSRF en cookie
httpOnly, échange du code, récupération des pages administrées. Aucun jeton ne
transite par une URL — les cibles sont relistées côté serveur au moment de la
sélection.
- Facebook : jeton utilisateur longue durée puis jeton de PAGE, qui n'expire
pas. LinkedIn : jeton 60 jours, rafraîchi automatiquement si l'application a
obtenu les jetons de rafraîchissement programmatiques.
- Faute de quoi le backoffice affiche la date d'expiration, un bandeau sur le
tableau de bord à J-7 et un bouton Reconnecter.
- `social.ts` lit les identifiants via `social-accounts.ts` : base d'abord,
variables d'environnement ensuite. Les installations existantes continuent de
fonctionner sans modification.
- Le secret d'application n'est jamais renvoyé au navigateur : champ vide =
valeur conservée.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
Bloc « Informations pratiques » (fiche adhérent)
- Suppression des dégradés hors palette (radial doré de la section, dégradés
crème/bleu des deux sous-cartes) au profit du vocabulaire visuel du site :
carte blanche bordée, sous-cartes plates sur le fond de champ, variables CSS.
- La pastille « Préparer votre visite » devient une puce arrondie identique aux
badges du reste du site ; le chip d'ouverture reprend les teintes de statut.
- Les glyphes texte ◷ et ↗ sont remplacés par des icônes SVG cohérentes.
Suspension des promotions
- Nouveau statut `suspended` + colonnes `suspended_by` / `suspended_by_id` /
`suspended_at`.
- L'adhérent suspend et réactive ses propres promotions depuis Mon espace ;
l'association suspend et réactive n'importe laquelle depuis le backoffice.
- Une suspension décidée par l'association ne peut pas être levée par
l'adhérent : le motif est affiché dans son espace.
- Les lectures publiques filtrant déjà sur `live`, une promo suspendue quitte
immédiatement l'accueil, l'annuaire et la fiche.
Publication Facebook / LinkedIn
- `src/lib/social.ts` : publication d'une promo en ligne sur la page Facebook
(Graph API, upload multipart car les images sont en data-URI) et sur la page
LinkedIn (Posts API + Images API en trois étapes).
- Jetons d'accès exclusivement en variables d'environnement ; un réseau non
configuré masque son bouton.
- Table `social_posts` : chaque tentative est tracée, un échec est rattrapé et
affiché sur la carte de la promo sans interrompre le backoffice.
- Nouvelle capacité RBAC `publishSocial` (admin + modérateur).
Liens sociaux publics
- Réglages `association_facebook` / `association_linkedin` éditables dans
Paramètres, préremplis avec les pages de l'association.
- Section « Plein R sur les réseaux » sur l'accueil et icônes dans le pied de
page, masquées si le réglage est vide.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
La redirection vers /login utilisait http://localhost:8413 (ancienne valeur par
défaut d'AUTH_URL). On retire ce défaut localhost : AUTH_URL prend la valeur
fournie (ex. https://pleinr.ffnancy.fr) et pilote la redirection. Ajout de
AUTH_TRUST_HOST=true (confiance aux en-têtes X-Forwarded-*) et l'entrypoint
supprime AUTH_URL si vide. Vérifié : la redirection pointe vers le domaine public.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL
Le nom générique "postgres" entrait en collision avec d'autres conteneurs
"postgres" présents sur le réseau proxy partagé (nginx_default), d'où les
ECONNREFUSED vers de mauvaises IP. On utilise désormais pleinr-postgres,
unique et présent uniquement sur le réseau interne.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL
Le compose ne requiert plus AUTH_SECRET (`:?` retiré). Si la variable est vide,
docker-entrypoint.sh génère un secret aléatoire et le persiste dans le volume
app-data (/app/data/auth_secret), réutilisé aux redémarrages. Surcharge possible
en définissant AUTH_SECRET. Déploiement Portainer sans configuration requise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL
- App : 8413 (hôte) → 3000 (conteneur)
- Postgres : 54329 (hôte) → 5432 (conteneur)
AUTH_URL, .env.example et README mis à jour en conséquence. Les ports internes
des conteneurs (3000, 5432) et DATABASE_URL (réseau interne) restent inchangés.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwfhYHzC9hQWPDnQ2p53GL