Files
SBC/README.md
T
Claude 96dca2a802 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
2026-07-10 19:43:07 +00:00

138 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```