Files
PleinR/docker-compose.yml
T
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

97 lines
4.0 KiB
YAML

# Plein R — single application container + external Postgres container.
#
# The app (Next.js) runs in ONE container. Postgres runs as a SEPARATE
# (external) container. For a managed/already-existing Postgres, delete the
# `postgres` service below and point DATABASE_URL at your instance.
services:
postgres:
image: postgres:16-alpine
container_name: pleinr-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-pleinr}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-pleinr}
POSTGRES_DB: ${POSTGRES_DB:-pleinr}
volumes:
- pgdata:/var/lib/postgresql/data
ports:
# Lié à 127.0.0.1 : la base ne doit jamais être joignable depuis le réseau.
# Retirez le préfixe uniquement pour un accès distant volontaire (et protégé).
- "127.0.0.1:54329:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-pleinr} -d ${POSTGRES_DB:-pleinr}"]
interval: 5s
timeout: 5s
retries: 12
networks:
- internal
app:
build: .
container_name: pleinr-app
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
# Use the UNIQUE container name (not the generic "postgres" alias) to avoid
# name collisions with other "postgres" containers on the shared proxy
# network. pleinr-postgres only exists on our internal network.
DATABASE_URL: postgres://${POSTGRES_USER:-pleinr}:${POSTGRES_PASSWORD:-pleinr}@pleinr-postgres:5432/${POSTGRES_DB:-pleinr}
# Optional: if left empty, the container generates and persists one
# automatically (see docker-entrypoint.sh + the app-data volume).
AUTH_SECRET: ${AUTH_SECRET:-}
# IMPORTANT behind a reverse proxy: set AUTH_URL to your public URL so the
# login redirect points to the right host, e.g.
# AUTH_URL=https://pleinr.ffnancy.fr
AUTH_URL: ${AUTH_URL:-}
# Lets Auth.js trust the X-Forwarded-* headers from the proxy.
AUTH_TRUST_HOST: "true"
# URL publique, reprise dans le texte des posts Facebook / LinkedIn.
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-}
# Chiffrement des jetons réseaux stockés en base (à défaut : AUTH_SECRET).
SOCIAL_TOKEN_KEY: ${SOCIAL_TOKEN_KEY:-}
# Repli historique : la configuration normale se fait dans le backoffice.
FACEBOOK_PAGE_ID: ${FACEBOOK_PAGE_ID:-}
FACEBOOK_PAGE_ACCESS_TOKEN: ${FACEBOOK_PAGE_ACCESS_TOKEN:-}
FACEBOOK_GRAPH_VERSION: ${FACEBOOK_GRAPH_VERSION:-}
LINKEDIN_ORGANIZATION_URN: ${LINKEDIN_ORGANIZATION_URN:-}
LINKEDIN_ORGANIZATION_ID: ${LINKEDIN_ORGANIZATION_ID:-}
LINKEDIN_ACCESS_TOKEN: ${LINKEDIN_ACCESS_TOKEN:-}
LINKEDIN_API_VERSION: ${LINKEDIN_API_VERSION:-}
SEED_ON_START: ${SEED_ON_START:-true}
SEED_ADMIN_EMAIL: ${SEED_ADMIN_EMAIL:-admin@plein-r.fr}
# Vide = un mot de passe aléatoire est généré au premier démarrage et
# affiché une seule fois dans les journaux du conteneur.
SEED_ADMIN_PASSWORD: ${SEED_ADMIN_PASSWORD:-}
# Comptes de démonstration (mot de passe connu) : jamais en production.
SEED_DEMO: ${SEED_DEMO:-false}
SEED_ADMIN_NAME: ${SEED_ADMIN_NAME:-Administrateur Plein R}
ports:
# Host port rarely used (container stays on 3000). App reachable at http://HOST:8413
- "8413:3000"
volumes:
# Persists the auto-generated AUTH_SECRET across restarts.
- appdata:/app/data
networks:
# "internal" is shared with Postgres; "proxy" is the external network
# shared with the reverse proxy (e.g. Nginx Proxy Manager) so it can reach
# the app by name: forward to pleinr-app:3000
- internal
- proxy
volumes:
pgdata:
appdata:
networks:
# Private network for app <-> Postgres communication.
internal:
driver: bridge
proxy:
# The reverse proxy's existing network. Must already exist (created by the
# NPM/nginx stack). Override the name with PROXY_NETWORK if it differs.
external: true
name: ${PROXY_NETWORK:-nginx_default}