- Nouveau rôle users.role='moderator' (contrainte CHECK migrée via
DROP+ADD CONSTRAINT, idempotent) : accès à Membres, Rencontres,
Inscriptions uniquement. Dashboard, Catégories (écriture), Contenu du
site, Demandes d'adhésion et gestion des comptes restent admin-only,
imposé côté serveur (requireAuth accepte désormais un tableau de rôles)
- Nouvel onglet « Administrateurs » (admin-only) : créer un compte admin
ou modérateur (mot de passe temporaire généré, même mécanique que pour
les membres — visible tant que non changé, changement forcé à la
première connexion), réinitialiser l'accès, promouvoir/rétrograder,
supprimer. Garde-fous : impossible de se supprimer ou de se rétrograder
soi-même, impossible de supprimer le dernier administrateur
- users.full_name (colonne migrée) pour l'affichage des comptes staff
- AccessCell/CredentialsModal extraits dans AccessControls.jsx, partagés
entre la gestion des membres et celle des comptes admin/modérateur
- AdminShell filtre ses onglets par rôle ; Espace.jsx route admin et
modérateur vers le back-office
Corrigé en cours de route : GET /api/admin/categories doit rester lisible
par les modérateurs (nécessaire au formulaire membre) même si sa gestion
en écriture reste admin-only — sans quoi le Promise.all du frontend
échouait silencieusement et vidait la liste des membres.
Vérifié : 34 + 21 tests existants toujours au vert, 26 nouveaux tests de
permissions par rôle, parcours navigateur complet (création modérateur,
connexion, changement forcé, menu restreint, accès aux membres).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
- Nouveau membre créé avec email : un mot de passe temporaire aléatoire
(~69 bits, sans caractères ambigus) est généré et haché ; il est retourné
à l'admin et reste lisible en clair (users.temp_password) tant que le
membre ne l'a pas changé
- Colonnes users.temp_password / must_change_password (migration
ALTER TABLE IF NOT EXISTS, appliquée au démarrage par le bootstrap)
- POST /api/admin/members/:id/reset-access : régénère l'accès (création
s'il n'existait pas, ou réinitialisation si le membre a perdu son mot
de passe) ; login/me exposent mustChangePassword ; change-password
efface systématiquement le mot de passe temporaire et lève le blocage
- Frontend : colonne « Accès » dans la liste des membres (mot de passe
visible + copier + réinitialiser, ou « Défini », ou « Créer l'accès »),
modal de confirmation après création/réinitialisation, écran de
changement de mot de passe obligatoire avant tout accès au portail ou
au back-office
Vérifié : cycle complet (création → mot de passe visible côté admin →
connexion → changement forcé → effacement automatique → réinitialisation
admin en cas de perte) via 21 tests e2e dédiés + parcours navigateur ;
34 tests existants toujours au vert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
Résout définitivement « Role sbc_app does not exist » : l'initialisation
ne dépend plus de docker-entrypoint-initdb.d ni de bind mounts du dépôt
(fragiles selon le mode de déploiement Portainer).
- Nouveau bootstrap idempotent exécuté par l'app à chaque démarrage avec
le superuser (PG_SUPERUSER_PASSWORD) : création de la base et du rôle
sbc_app si absents, réalignement du mot de passe, schéma en
CREATE ... IF NOT EXISTS, données de démo si la base est vide, grants
- Schéma et seed embarqués dans l'image (server/src/sql), seed par
recherche de noms (indépendant des séquences)
- Compose réduit à deux services (db + app), suppression de db-sync et
du montage ./db/init ; répertoire db/ retiré
Vérifié : volume totalement vierge auto-initialisé, redémarrage sans
re-seed, changement d'APP_DB_PASSWORD auto-réparé, 34 tests e2e verts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
Plus aucune référence au port 3000 : l'application écoute sur 8321 dans
le conteneur (PORT, EXPOSE, healthcheck, mapping 8321:8321, upstream
nginx sbc-app:8321, proxy de dev Vite, README).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
- Le service app rejoint le réseau externe nginx_default (nom configurable
via PROXY_NETWORK) avec l'alias stable « sbc-app » pour l'upstream nginx
(proxy_pass http://sbc-app:3000)
- La base de données reste sur le réseau interne de la stack uniquement
- README : section reverse-proxy (création du réseau, TRUST_PROXY,
COOKIE_SECURE, en-têtes à transmettre)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
- Nouveau service one-shot db-sync : à chaque démarrage de la stack, il
réaligne le mot de passe du rôle sbc_app sur APP_DB_PASSWORD (corrige
« password authentication failed » quand le volume a été initialisé
avec une ancienne valeur) ; l'app démarre après sa réussite
- Port hôte PostgreSQL : 56432 → 58412 (conflit avec un service existant),
toujours en loopback uniquement
- Note de dépannage dans le README
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
- docker-compose : valeurs par défaut pour POSTGRES_PASSWORD,
APP_DB_PASSWORD et les mots de passe initiaux (admin SlucAdmin2026!,
membres SlucMembre2026!) — surchargeables par variables d'environnement
- JWT_SECRET devient optionnel : l'application génère un secret aléatoire
au démarrage s'il est absent (les sessions expirent alors au redémarrage
du conteneur), aucun secret de signature n'est committé
- .env.example et README mis à jour (démarrage clé en main, consignes de
surcharge pour la production)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
- Un seul conteneur applicatif : Express sert l'API, le frontend React
compilé (fallback SPA, cache immutable sur /assets) et les images
/uploads (nosniff + CSP default-src 'none')
- En-têtes de sécurité (CSP stricte, X-Frame-Options DENY, etc.) portés
de nginx vers helmet, compression gzip ajoutée
- Application exposée sur le port 8321 (APP_PORT), PostgreSQL sur
127.0.0.1:56432 (DB_PORT, loopback uniquement pour l'admin locale)
- TRUST_PROXY pilote la confiance aux en-têtes X-Forwarded-* (désactivé
par défaut hors reverse-proxy)
- Dockerfile multi-étages unique à la racine (build React → deps → image
finale non-root, fs en lecture seule)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o