Claude 3b17d7ff42 Durcissement (lot A) : seed sûr en production, mots de passe temporaires affichés une seule fois
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
2026-09-04 09:15:45 +00:00

Plein R

Site de l'association Plein R — les commerçants et entreprises du Bassin de Pompey. Réseau · Rencontre · Réussite.

Deux surfaces, une seule application :

  • Accueil (/) — vitrine publique : héro + recherche, métiers, promotions des adhérents, mises à l'honneur.
  • Backend (/backend) — back-office authentifié avec rôles : tableau de bord, adhérents, modération des promotions, administrateurs, et l'espace adhérent.

Stack

Couche Choix
Framework Next.js 15 (App Router, TypeScript, sortie standalone)
Base de données PostgreSQL 16 (conteneur externe)
Accès données Drizzle ORM + drizzle-kit (migrations versionnées) sur le driver pg
Authentification Auth.js v5 (NextAuth) — provider credentials, sessions JWT, RBAC
Conteneurisation 1 conteneur applicatif + 1 conteneur Postgres (docker-compose)

L'application tourne dans un seul conteneur Docker. Postgres est un conteneur séparé (externe).

Démarrage rapide (Docker)

cp .env.example .env
# AUTH_SECRET est optionnel : s'il est vide, le conteneur en génère un et le
# persiste automatiquement. Pour le fixer vous-même : openssl rand -base64 32

docker compose up --build

Au démarrage, le conteneur applique les migrations puis (si SEED_ON_START=true) charge des données de démonstration. Ensuite :

Ports (volontairement peu courants pour éviter les conflits) : l'app est publiée sur l'hôte en 8413 (→ 3000 dans le conteneur) et Postgres en 54329 (→ 5432). Modifiez la partie gauche des ports: dans docker-compose.yml si besoin. En dev local (npm run dev), l'app reste sur 3000.

Postgres déjà existant ? Supprimez le service postgres de docker-compose.yml et pointez DATABASE_URL vers votre instance.

Comptes de démonstration (seed)

E-mail Mot de passe Rôle
admin@plein-r.fr changeme123 Administrateur
claire@plein-r.fr changeme123 Administrateur
thomas@plein-r.fr changeme123 Modérateur
sophie@plein-r.fr changeme123 Éditeur
contact@aubonpain.fr changeme123 Adhérent (Au Bon Pain)

Rôles & permissions

Capacité admin moderator editor member
Tableau de bord ✅ ✅ ✅ —
Adhérents (CRUD) ✅ ✅ ✅ —
Modération des promotions ✅ ✅ — —
Publication réseaux sociaux ✅ ✅ — —
Administrateurs ✅ — — —
Mon espace (publier une promo) — — — ✅

/backend est protégé par le middleware ; chaque vue affine l'accès selon le rôle.

Cycle de vie d'une promotion

pending → live → suspended ⇄ live, ou suppression.

  • L'adhérent soumet la promotion depuis Mon espace : elle part en pending.
  • L'association la valide dans Backend › Promotions : elle passe live et s'affiche sur l'accueil, l'annuaire et la fiche de l'adhérent.
  • Suspension : l'adhérent peut suspendre ses promotions depuis Mon espace, l'association peut suspendre n'importe laquelle. Une promotion suspendue sort immédiatement du site public et peut être remise en ligne à tout moment. Nuance importante : une suspension décidée par l'association ne peut pas être levée par l'adhérent (promotions.suspended_by mémorise l'auteur).

Publication sur Facebook / LinkedIn

La diffusion est pilotée par la validation, jamais déclenchée à la main :

  1. L'adhérent coche Facebook et/ou LinkedIn en soumettant sa promotion. Rien n'est publié à ce stade ; il peut modifier son choix tant que la promo est pending.
  2. Le modérateur voit ces cases pré-cochées sur la carte en attente et peut les ajuster — c'est le dernier moment où le choix est modifiable.
  3. Valider met la promo en ligne et publie sur les réseaux retenus (image + texte + lien vers la fiche adhérent). Après validation, le choix est figé.

Chaque tentative est tracée dans social_posts : succès avec le lien du post, ou échec avec le message d'erreur affiché sur la carte. En cas d'échec, un bouton Réessayer apparaît, limité aux réseaux déjà choisis — il ne permet pas d'élargir la diffusion.

Une promotion n'est jamais publiée deux fois : un réseau ayant déjà une publication réussie est systématiquement ignoré, y compris sur un cycle suspension → remise en ligne.

Connecter les comptes

Tout se passe dans Backend › Réseaux sociaux (administrateurs) : on colle les identifiants de l'application, on clique Connecter, on choisit la page. Les jetons sont récupérés par OAuth et stockés chiffrés (AES-256-GCM, clé SOCIAL_TOKEN_KEY ou à défaut AUTH_SECRET) ; ils ne ressortent jamais vers le navigateur. Un réseau non connecté voit simplement sa case disparaître du formulaire de promotion.

Le même écran porte l'URL publique du site, pré-remplie avec l'adresse par laquelle vous consultez le backoffice : elle sert à l'adresse de retour OAuth et au lien inséré dans les publications. Seule l'origine est conservée (le schéma est ajouté si vous l'omettez, un éventuel chemin est retiré).

L'écran affiche ensuite l'URL de redirection à déclarer sur le portail développeur — c'est l'erreur de configuration la plus fréquente.

Facebook. Créez une application « Business » sur developers.facebook.com, ajoutez le produit Connexion Facebook, déclarez l'URL de redirection. Gardez l'application en mode développement avec le compte de l'association comme administrateur : publier sur votre propre page ne demande alors aucune revue Meta. Le jeton de page obtenu n'expire pas — une connexion suffit, définitivement.

LinkedIn. Créez une application sur linkedin.com/developers rattachée à la page de l'association, puis demandez le produit Community Management API. Deux limites à connaître avant de vous lancer :

  • l'accès est soumis à une revue (page vérifiée, nom légal, adresse, politique de confidentialité) ; ce n'est pas garanti ni immédiat ;
  • les jetons LinkedIn durent 60 jours et le rafraîchissement programmatique est réservé à certains partenaires. En pratique il faut donc recliquer sur Reconnecter environ tous les deux mois. Le backoffice affiche la date d'expiration et un bandeau d'alerte 7 jours avant.

Les variables d'environnement (FACEBOOK_PAGE_ACCESS_TOKEN, etc.) restent lues en repli si aucun compte n'est connecté, pour ne pas casser une installation antérieure.

Les liens publics vers les deux pages (affichés sur l'accueil et dans le pied de page) se règlent, eux, dans Backend › Paramètres.

Développement local (sans Docker)

npm install
# Postgres accessible via DATABASE_URL (voir .env.example)
npm run db:generate   # (re)génère le SQL de migration depuis le schéma
npm run db:migrate    # applique les migrations
npm run db:seed       # données de démonstration
npm run dev           # http://localhost:3000

Scripts

Script Rôle
npm run dev Serveur de développement
npm run build Build de production (standalone)
npm run build:scripts Bundle des scripts migrate/seed (utilisé par Docker)
npm run db:generate Génère les migrations Drizzle
npm run db:migrate Applique les migrations
npm run db:seed Insère les données de démonstration
npm test Tests de sécurité (filtre XSS, limitation de connexion, chiffrement)

Variables d'environnement

Voir .env.example. Les principales :

  • DATABASE_URL — chaîne de connexion Postgres
  • AUTH_SECRET — secret de signature des sessions (obligatoire)
  • AUTH_URL — URL publique de l'application
  • SEED_ON_START — true pour seeder au démarrage du conteneur
  • SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD / SEED_ADMIN_NAME — premier admin
  • NEXT_PUBLIC_SITE_URL — repli pour l'URL publique du site ; elle se règle normalement dans Backend › Réseaux sociaux, aucune variable n'est requise.
  • SOCIAL_TOKEN_KEY — clé de chiffrement des jetons réseaux (défaut : AUTH_SECRET)
  • FACEBOOK_PAGE_ID / FACEBOOK_PAGE_ACCESS_TOKEN, LINKEDIN_ORGANIZATION_URN / LINKEDIN_ACCESS_TOKEN — repli si aucun compte n'est connecté via le backoffice

Le logo (public/assets/logo.svg) est une recréation vectorielle aux couleurs de la marque. Remplacez ce fichier par le logo officiel quand vous le souhaitez (les pages le référencent via /assets/logo.svg).

Architecture du dépôt

src/
├── app/
│   ├── page.tsx                  # Accueil (public)
│   ├── login/                    # connexion
│   ├── api/auth/[...nextauth]/   # routes Auth.js
│   └── backend/                  # back-office
│       ├── layout.tsx            # garde d'auth + shell
│       ├── page.tsx              # tableau de bord
│       ├── adherents/            # adhérents (liste, ajout, édition)
│       ├── promotions/           # modération
│       ├── administrateurs/      # gestion des accès
│       ├── espace/               # espace adhérent (publication)
│       └── actions.ts            # server actions (mutations)
├── db/                           # schéma Drizzle, client, migrate, seed
├── lib/                          # requêtes, RBAC
└── types/                        # augmentation des types Auth.js
S
Description
No description provided
Readme
2.4 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 95.6%
CSS 3.9%
JavaScript 0.3%
Dockerfile 0.1%
Shell 0.1%