Application complète SLUC Business Club (React + Express + PostgreSQL, Docker)

Implémente l'intégralité de la maquette « SLUC Business Club.dc.html » :
- Site public : accueil (héro administrable, carrousel membres, agenda avec
  inscription en ligne, rencontres passées, demande d'adhésion), annuaire
  avec recherche/filtres/fiche détaillée, page association
- Espace membre : connexion, édition de fiche avec aperçu direct, upload
  logo/photo, statut d'adhésion par saison, changement de mot de passe
- Espace admin : tableau de bord, membres (validation par saison),
  rencontres (CRUD + inscrits + impression + export Excel), inscriptions,
  catégories, contenu du site

Architecture : 3 conteneurs Docker Compose — PostgreSQL 16 (réseau interne,
rôle applicatif restreint), API Express (non-root, read-only fs), nginx
non privilégié (frontend React + reverse-proxy + CSP stricte).

Sécurité : requêtes 100 % paramétrées, bcrypt + JWT httpOnly SameSite=Strict,
vérification d'Origin (CSRF), validation zod, rate limiting, uploads vérifiés
par octets magiques avec noms aléatoires, aucun secret committé.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fkg15RCxNgUys4ru73He2o
This commit is contained in:
Claude committed 2026-07-10 19:43:07 +00:00
commit 96dca2a802
50 files changed
+6887

No files matched your search

+137
View File
@@ -0,0 +1,137 @@
# Business Club SLUC Nancy
Application web complète du réseau d'affaires des partenaires du SLUC Nancy Basket :
site public (accueil, annuaire des membres, association), espace membre et
back-office d'administration.
## Architecture
Trois conteneurs orchestrés par Docker Compose :
| Service | Rôle | Exposition |
|---------|------|------------|
| `db` | PostgreSQL 16 (schéma + données de démo au premier démarrage) | réseau interne uniquement, aucun port publié |
| `api` | API REST Node.js 22 / Express (auth, membres, rencontres, inscriptions, catégories, contenu, uploads) | réseau interne uniquement |
| `web` | nginx (non-root) : frontend React compilé, reverse-proxy `/api`, service des images `/uploads` | port `8080` |
```
Navigateur ──> web (nginx :8080) ──> api (Express :3000) ──> db (PostgreSQL :5432)
│ /uploads (volume partagé, lecture seule)
└─ fichiers statiques React
```
- **Frontend** : React 18 + Vite + React Router — reproduction fidèle de la maquette
(`SLUC Business Club.dc.html`).
- **Backend** : Express, `pg` (requêtes paramétrées), `zod` (validation), `bcryptjs`
(hachage), JWT en cookie httpOnly, `helmet`, `express-rate-limit`, `multer` (uploads).
- **Base** : PostgreSQL 16, initialisée par `db/init/` (rôle applicatif restreint,
schéma, données de démonstration).
## 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
```
L'application est disponible sur <http://localhost:8080> (port configurable via `WEB_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 :
| 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` |
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`).
## Fonctionnalités
**Site public**
- Accueil : héro éditorial (citation et photo administrables), carrousel des membres,
prochaines rencontres avec inscription en ligne (contrôle de capacité et de doublon),
rencontres passées, formulaire de demande d'adhésion.
- Annuaire : recherche plein texte, filtres par catégorie, fiche détaillée par entreprise.
Seuls les membres **validés pour la saison** apparaissent.
- L'association : mission, valeurs, mot du président, chronologie, chiffres clés.
**Espace membre** (connexion email + mot de passe)
- Édition de sa fiche annuaire avec aperçu en direct.
- Upload du logo et de la photo dirigeant·e (JPEG/PNG/WebP, 2 Mo max, vérification
des octets magiques côté serveur).
- Bannière d'état d'adhésion (validée / à renouveler) et changement de mot de passe.
**Espace admin** (rôle `admin`)
- Tableau de bord : indicateurs temps réel, dernières inscriptions, prochaines rencontres.
- Membres : création, édition, validation/suspension par saison (1er sept. → 31 août).
- Rencontres : création, édition, suppression ; liste des inscrits avec impression
et export Excel.
- Inscriptions : modification, confirmation, annulation avec confirmation.
- Catégories : ajout, renommage, suppression (les entreprises deviennent « Non classée »).
- Contenu du site : citation, signature et photo de la page d'accueil.
## Sécurité
- **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.
- **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
les mutations.
- **Autorisation** : middleware de rôles (`member` / `admin`) sur chaque route protégée ;
un membre ne peut modifier que sa propre fiche.
- **Validation** : schémas `zod` sur toutes les entrées (types, longueurs, formats),
contraintes `CHECK` en base en seconde ligne.
- **Rate limiting** : global (300/min), connexion (10 / 15 min), formulaires publics
(20 / h).
- **Uploads** : taille ≤ 2 Mo, type vérifié par octets magiques (jamais le MIME client),
nom de fichier aléatoire généré côté serveur (aucune traversée de chemin possible),
servis par nginx avec `X-Content-Type-Options: nosniff` et types MIME forcés.
- **En-têtes** : CSP stricte, `X-Frame-Options: DENY`, `Referrer-Policy`,
`Permissions-Policy`, `helmet` côté API, `server_tokens off`.
- **Conteneurs** : API en utilisateur non-root avec système de fichiers en lecture
seule (`read_only` + tmpfs), nginx non privilégié, `no-new-privileges`, PostgreSQL
sans port publié sur un réseau interne (`internal: true`).
- **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é.
- **Erreurs** : les détails restent dans les logs serveur, les clients reçoivent un
message générique.
### Pour la production
- Placez l'application derrière HTTPS (reverse-proxy TLS) et passez `COOKIE_SECURE=true`.
- Changez immédiatement les mots de passe initiaux.
- Sauvegardez les volumes `db_data` (base) et `uploads` (images).
## Développement local (sans Docker)
```bash
# Terminal 1 — base de données PostgreSQL locale + variables d'env, puis :
cd server && npm install && npm start
# Terminal 2
cd web && npm install && npm run dev # proxy /api → localhost:3000
```
## Structure
```
├── docker-compose.yml
├── .env.example
├── db/init/ # 01 rôle applicatif · 02 schéma · 03 données de démo
├── server/ # API Express (src/routes, src/middleware, uploads)
└── web/ # React + Vite, nginx.conf, Dockerfile multi-étages
```