Revue complète : sécurité, optimisations et UI/UX mobile

Sécurité
- JWT : un JWT_SECRET d'exemple ou trop court est ignoré au profit d'un
  secret aléatoire conservé en base (avant, la valeur publique de
  docker-compose.yml permettait de forger un jeton admin).
- Le compte est relu en base à chaque requête : désactivation, changement
  de rôle et réinitialisation du mot de passe prennent effet immédiatement.
- Connexion : 10 échecs max par e-mail / 15 min, temps constant que l'e-mail
  existe ou non, mot de passe admin retiré des logs.
- Validation des entrées (ids, dates, mois, longueurs, e-mail) : 400 au
  lieu de 500. Un admin ne peut plus se désactiver ni se rétrograder.
- Mots de passe : 8 caractères minimum ; changement en libre-service.
- Export : nom de fichier conforme RFC 5987 (un nom de société avec « — »
  faisait planter l'export).
- esc() échappe aussi les guillemets (injection d'attributs HTML).
- En-têtes CSP / X-Frame-Options / nosniff (Nginx + API), API et Postgres
  publiés sur 127.0.0.1 seulement, image backend non-root via npm ci.

Optimisations
- Export : une requête pour tout le mois au lieu d'une par cadre.
- Saisie groupée : un seul INSERT (unnest), doublons dédupliqués.
- Dates renvoyées en chaînes (plus de décalage d'un jour selon le TZ).
- Planning : chaque clic met à jour l'affichage localement au lieu de
  recharger le mois ; les réponses de mois périmées sont ignorées.
- Recherche Utilisateurs / Plannings filtrée localement (plus une requête
  par touche) ; statuts des sociétés chargés en parallèle.

UI/UX
- Mobile : toutes les destinations dans la barre basse (Sociétés et Ma
  saisie étaient inaccessibles à l'admin) ; tableaux affichés en cartes
  (les boutons d'action étaient hors écran).
- Boutons désactivés pendant l'envoi, focus et Échap dans les dialogues,
  retour à l'écran de connexion quand la session est révoquée, erreurs
  d'export affichées au lieu d'un fichier JSON téléchargé, actions
  destructives signalées, confirmation avant désactivation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D5Bdayziw6tybgqETZoNSt
This commit is contained in:
Claude committed 2026-10-09 07:17:54 +00:00
1 parent da875afb73
commit 3df521ac30
22 files changed
+830 -415

No files matched your search

+13 -7
View File
@@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path');
const bcrypt = require('bcryptjs');
const db = require('./db');
const { initJwtSecret } = require('./middleware/auth');
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
@@ -32,13 +33,17 @@ async function runMigrations() {
);
if (rows[0].exists) {
console.log('Schéma déjà initialisé.');
return;
} else {
console.log('Initialisation du schéma de base de données...');
await db.query(readMigration('001_init.sql'));
console.log('Schéma créé.');
}
const sqlPath = path.join(__dirname, '..', 'migrations', '001_init.sql');
const sql = fs.readFileSync(sqlPath, 'utf8');
console.log('Initialisation du schéma de base de données...');
await db.query(sql);
console.log('Schéma créé.');
// Idempotent, so safe on every start: brings existing databases up to date.
await db.query(readMigration('002_security.sql'));
}
function readMigration(name) {
return fs.readFileSync(path.join(__dirname, '..', 'migrations', name), 'utf8');
}
async function seedAdmin() {
@@ -60,7 +65,7 @@ async function seedAdmin() {
console.log('========================================================');
console.log(' Compte administrateur créé :');
console.log(` Email : ${email}`);
console.log(` Mot de passe : ${password}`);
console.log(' Mot de passe : celui de ADMIN_PASSWORD');
console.log(' Merci de le changer après la première connexion.');
console.log('========================================================');
}
@@ -68,6 +73,7 @@ async function seedAdmin() {
async function bootstrap() {
await waitForDb();
await runMigrations();
await initJwtSecret();
await seedAdmin();
}