Prérègle les secrets : déploiement sans configuration (Portainer)
- 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
This commit is contained in:
4 files changed
+55
-33
No files matched your search
+6
-3
@@ -1,5 +1,6 @@
|
||||
# Copy this file to .env and fill in strong secrets before running.
|
||||
# cp .env.example .env
|
||||
# OPTIONAL: the stack starts without any .env thanks to preconfigured
|
||||
# defaults in docker-compose.yml. For production, copy this file to .env
|
||||
# (or set the variables in Portainer) and override everything below.
|
||||
# openssl rand -hex 32 # use for JWT_SECRET
|
||||
# openssl rand -hex 24 # use for each DB password
|
||||
|
||||
@@ -9,7 +10,9 @@ POSTGRES_PASSWORD=change-me-postgres-superuser
|
||||
# Password of the restricted application role (sbc_app) the API connects with
|
||||
APP_DB_PASSWORD=change-me-app-db-password
|
||||
|
||||
# Secret used to sign session tokens (JWT). MUST be long and random.
|
||||
# Secret used to sign session tokens (JWT), at least 32 characters.
|
||||
# If unset, the app generates a random one at startup (sessions are then
|
||||
# invalidated whenever the container restarts).
|
||||
JWT_SECRET=change-me-64-hex-chars-min
|
||||
|
||||
# Initial password of the admin account (admin@sluc-businessclub.fr).
|
||||
|
||||
@@ -34,27 +34,29 @@ le réseau. Supprimez la section `ports:` du service `db` pour la fermer totalem
|
||||
## Démarrage
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Éditez .env : générez des secrets forts
|
||||
# openssl rand -hex 32 → JWT_SECRET
|
||||
# openssl rand -hex 24 → POSTGRES_PASSWORD et APP_DB_PASSWORD
|
||||
# puis choisissez ADMIN_INITIAL_PASSWORD / MEMBER_INITIAL_PASSWORD
|
||||
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Aucune configuration n'est requise : des valeurs par défaut sont préréglées
|
||||
(mots de passe de base de données, comptes initiaux) et le secret JWT est
|
||||
généré aléatoirement au démarrage s'il n'est pas fourni. Compatible avec un
|
||||
déploiement direct depuis Git dans Portainer.
|
||||
|
||||
Pour la production, surchargez ces valeurs via un fichier `.env`
|
||||
(voir `.env.example`) ou les variables d'environnement de Portainer.
|
||||
|
||||
L'application est disponible sur <http://localhost:8321> (port configurable via `APP_PORT`).
|
||||
|
||||
## Comptes
|
||||
|
||||
Les comptes de démonstration sont créés **verrouillés** (aucun mot de passe en dur
|
||||
dans le dépôt). Au démarrage, l'API leur applique le mot de passe des variables
|
||||
d'environnement — uniquement s'ils n'en ont pas déjà un :
|
||||
Les comptes de démonstration sont créés **verrouillés** en base. Au démarrage,
|
||||
l'API leur applique le mot de passe des variables d'environnement — uniquement
|
||||
s'ils n'en ont pas déjà un :
|
||||
|
||||
| Rôle | Email | Mot de passe initial |
|
||||
|------|-------|----------------------|
|
||||
| Admin | `admin@sluc-businessclub.fr` | `ADMIN_INITIAL_PASSWORD` |
|
||||
| Membre (×12) | email de contact de chaque entreprise de démo, ex. `contact@lorraine-assurances.fr` | `MEMBER_INITIAL_PASSWORD` |
|
||||
| Rôle | Email | Mot de passe initial (défaut) |
|
||||
|------|-------|-------------------------------|
|
||||
| Admin | `admin@sluc-businessclub.fr` | `ADMIN_INITIAL_PASSWORD` (défaut : `SlucAdmin2026!`) |
|
||||
| Membre (×12) | email de contact de chaque entreprise de démo, ex. `contact@lorraine-assurances.fr` | `MEMBER_INITIAL_PASSWORD` (défaut : `SlucMembre2026!`) |
|
||||
|
||||
Changez le mot de passe admin après la première connexion (Espace membre/admin →
|
||||
formulaire « Mot de passe », endpoint `POST /api/auth/change-password`).
|
||||
@@ -88,8 +90,8 @@ formulaire « Mot de passe », endpoint `POST /api/auth/change-password`).
|
||||
|
||||
- **Injection SQL** : 100 % de requêtes paramétrées (`pg`), aucun SQL concaténé.
|
||||
- **Authentification** : bcrypt (coût 12), comparaison à temps constant même si
|
||||
l'email est inconnu, JWT HS256 signé (secret ≥ 32 caractères exigé au démarrage),
|
||||
session de 12 h.
|
||||
l'email est inconnu, JWT HS256 signé (secret fourni via `JWT_SECRET` ou généré
|
||||
aléatoirement au démarrage), session de 12 h.
|
||||
- **Cookies** : `httpOnly`, `SameSite=Strict`, `Secure` activable (`COOKIE_SECURE=true`
|
||||
derrière HTTPS).
|
||||
- **CSRF** : cookie SameSite=Strict + vérification de l'en-tête `Origin` sur toutes
|
||||
@@ -110,8 +112,10 @@ formulaire « Mot de passe », endpoint `POST /api/auth/change-password`).
|
||||
uniquement sur `127.0.0.1` (inaccessible depuis le réseau).
|
||||
- **Base de données** : l'API se connecte avec un rôle dédié `sbc_app` limité au DML
|
||||
(pas de DDL, pas de superuser).
|
||||
- **Secrets** : uniquement via `.env` (ignoré par git) ; `docker compose` refuse de
|
||||
démarrer sans eux ; aucun hash ni mot de passe committé.
|
||||
- **Secrets** : surchargeables via `.env` / variables d'environnement ; le secret
|
||||
JWT n'est jamais committé (généré aléatoirement si absent) ; les valeurs par
|
||||
défaut préréglées ne servent qu'au démarrage clé en main et doivent être
|
||||
remplacées en production.
|
||||
- **Erreurs** : les détails restent dans les logs serveur, les clients reçoivent un
|
||||
message générique.
|
||||
|
||||
@@ -119,7 +123,9 @@ formulaire « Mot de passe », endpoint `POST /api/auth/change-password`).
|
||||
|
||||
- Placez l'application derrière HTTPS (reverse-proxy TLS) et passez `COOKIE_SECURE=true`
|
||||
et `TRUST_PROXY=true`.
|
||||
- Changez immédiatement les mots de passe initiaux.
|
||||
- Surchargez les valeurs par défaut (`POSTGRES_PASSWORD`, `APP_DB_PASSWORD`,
|
||||
`JWT_SECRET`, mots de passe initiaux) et changez le mot de passe admin
|
||||
après la première connexion.
|
||||
- Sauvegardez les volumes `db_data` (base) et `uploads` (images).
|
||||
|
||||
## Développement local (sans Docker)
|
||||
|
||||
+10
-6
@@ -7,8 +7,10 @@ services:
|
||||
environment:
|
||||
POSTGRES_DB: sbc
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
||||
APP_DB_PASSWORD: ${APP_DB_PASSWORD:?set APP_DB_PASSWORD in .env}
|
||||
# Preconfigured defaults so the stack deploys without any .env
|
||||
# (Portainer, etc.). Override them in production.
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-sbc-pg-c7f3a91d284e6b05}
|
||||
APP_DB_PASSWORD: ${APP_DB_PASSWORD:-sbc-app-9e12d47ab8c3f605}
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
- ./db/init:/docker-entrypoint-initdb.d:ro
|
||||
@@ -28,10 +30,12 @@ services:
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: "3000"
|
||||
DATABASE_URL: postgres://sbc_app:${APP_DB_PASSWORD}@db:5432/sbc
|
||||
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env}
|
||||
ADMIN_INITIAL_PASSWORD: ${ADMIN_INITIAL_PASSWORD:-}
|
||||
MEMBER_INITIAL_PASSWORD: ${MEMBER_INITIAL_PASSWORD:-}
|
||||
DATABASE_URL: postgres://sbc_app:${APP_DB_PASSWORD:-sbc-app-9e12d47ab8c3f605}@db:5432/sbc
|
||||
# Empty by default: the app then generates a random ephemeral secret at
|
||||
# startup. Set a fixed value to keep sessions across restarts.
|
||||
JWT_SECRET: ${JWT_SECRET:-}
|
||||
ADMIN_INITIAL_PASSWORD: ${ADMIN_INITIAL_PASSWORD:-SlucAdmin2026!}
|
||||
MEMBER_INITIAL_PASSWORD: ${MEMBER_INITIAL_PASSWORD:-SlucMembre2026!}
|
||||
COOKIE_SECURE: ${COOKIE_SECURE:-false}
|
||||
TRUST_PROXY: ${TRUST_PROXY:-false}
|
||||
UPLOAD_DIR: /data/uploads
|
||||
|
||||
+15
-6
@@ -1,3 +1,5 @@
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
const required = (name) => {
|
||||
const v = process.env[name];
|
||||
if (!v) {
|
||||
@@ -7,10 +9,22 @@ const required = (name) => {
|
||||
return v;
|
||||
};
|
||||
|
||||
// Session-signing secret: taken from the environment when provided, otherwise
|
||||
// generated randomly at startup (never committed to the repo). With a generated
|
||||
// secret, sessions simply expire when the container restarts.
|
||||
let jwtSecret = process.env.JWT_SECRET || '';
|
||||
if (jwtSecret.length < 32) {
|
||||
jwtSecret = crypto.randomBytes(48).toString('hex');
|
||||
console.warn(
|
||||
'JWT_SECRET absent or shorter than 32 characters: using a random ephemeral secret. ' +
|
||||
'Sessions will not survive a restart — set JWT_SECRET to persist them.'
|
||||
);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
port: Number(process.env.PORT || 3000),
|
||||
databaseUrl: required('DATABASE_URL'),
|
||||
jwtSecret: required('JWT_SECRET'),
|
||||
jwtSecret,
|
||||
cookieSecure: process.env.COOKIE_SECURE === 'true',
|
||||
// set to "true" only when running behind a reverse proxy (TLS termination)
|
||||
trustProxy: process.env.TRUST_PROXY === 'true',
|
||||
@@ -21,8 +35,3 @@ export const config = {
|
||||
sessionTtlSeconds: 12 * 60 * 60,
|
||||
cookieName: 'sbc_session',
|
||||
};
|
||||
|
||||
if (config.jwtSecret.length < 32) {
|
||||
console.error('JWT_SECRET must be at least 32 characters long.');
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in new issue
Block a user