Files
SBC/server/src/middleware/security.js
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

46 lines
1.3 KiB
JavaScript

import rateLimit from 'express-rate-limit';
// CSRF defense-in-depth: session cookie is SameSite=Strict, and every
// state-changing request must additionally come from our own origin.
export function csrfOriginCheck(req, res, next) {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
const origin = req.headers.origin || '';
const host = req.headers['x-forwarded-host'] || req.headers.host || '';
if (origin) {
let originHost;
try {
originHost = new URL(origin).host;
} catch {
return res.status(403).json({ error: 'Origine invalide' });
}
if (originHost !== host) {
return res.status(403).json({ error: 'Origine non autorisée' });
}
}
next();
}
export const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
limit: 10,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Trop de tentatives de connexion. Réessayez dans 15 minutes.' },
});
export const publicFormLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
limit: 20,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Trop de requêtes. Réessayez plus tard.' },
});
export const globalLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 300,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Trop de requêtes.' },
});