chore(deps): add helmet, express-rate-limit, express-validator
Add production dependencies introduced in recent security commits. Add docs/ENVIRONMENT_VARIABLES.md documenting TRUST_PROXY, SECRET, SESSION_SECRET, and ALLOWED_ORIGINS with configuration guidance.
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
# Variables d'environnement — Adastra API
|
||||||
|
|
||||||
|
Référence pour les variables sensibles ou non-triviales. Pour les valeurs par défaut et la liste exhaustive, voir `config/env/.env.example`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TRUST_PROXY
|
||||||
|
|
||||||
|
**Fichier concerné :** `src/createApp.js`
|
||||||
|
**Utilisé par :** `express-rate-limit` (IP réelle du client), `req.ip`
|
||||||
|
|
||||||
|
### Principe
|
||||||
|
|
||||||
|
Indique à Express combien de proxies se trouvent entre internet et le process Node. Sans cette valeur, `req.ip` retourne l'IP du proxy (`127.0.0.1`) au lieu de l'IP du client réel, ce qui rend le rate limiting inefficace (toutes les requêtes semblent venir de la même IP).
|
||||||
|
|
||||||
|
### Valeurs selon la topologie
|
||||||
|
|
||||||
|
| Environnement | Topologie | Valeur |
|
||||||
|
|---|---|---|
|
||||||
|
| Dev local | Node exposé directement, pas de proxy | ne pas setter (variable absente) |
|
||||||
|
| Prod — serveur seul | nginx → Node sur le même serveur | `1` |
|
||||||
|
| Prod — cloud avec LB | nginx → load balancer → Node | `2` |
|
||||||
|
| Confiance totale | Tous les proxies de la chaîne `X-Forwarded-For` | `true` (déconseillé) |
|
||||||
|
|
||||||
|
> **Règle générale :** `TRUST_PROXY` = nombre de proxies entre internet et Node.
|
||||||
|
|
||||||
|
### Vérification empirique
|
||||||
|
|
||||||
|
Ajouter temporairement cette route et appeler `/debug-ip` depuis l'extérieur :
|
||||||
|
|
||||||
|
```js
|
||||||
|
app.get('/debug-ip', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
ip: req.ip,
|
||||||
|
ips: req.ips,
|
||||||
|
xForwardedFor: req.headers['x-forwarded-for']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- `req.ip` retourne l'IP réelle du client → valeur correcte
|
||||||
|
- `req.ip` retourne `127.0.0.1` ou l'IP du proxy → augmenter de 1
|
||||||
|
- `req.ips` est vide malgré un proxy → idem
|
||||||
|
|
||||||
|
**Supprimer la route après vérification.**
|
||||||
|
|
||||||
|
### Implémentation actuelle
|
||||||
|
|
||||||
|
```js
|
||||||
|
if (process.env.TRUST_PROXY) {
|
||||||
|
app.set('trust proxy', parseInt(process.env.TRUST_PROXY, 10) || process.env.TRUST_PROXY);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Le cast `parseInt(...) || process.env.TRUST_PROXY` permet de passer soit un entier (`"1"` → `1`) soit la chaîne `"true"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SECRET
|
||||||
|
|
||||||
|
**Fichier concerné :** `src/config/index.js`
|
||||||
|
**Utilisé par :** signature JWT, HMAC-SHA256 des API keys, secret de session Express
|
||||||
|
|
||||||
|
Valeur obligatoire en production — le process refuse de démarrer si elle est absente. Générer avec :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SESSION_SECRET
|
||||||
|
|
||||||
|
**Fichier concerné :** `src/createApp.js`
|
||||||
|
**Utilisé par :** `express-session`
|
||||||
|
|
||||||
|
Indépendant de `SECRET` pour limiter la surface d'impact si l'un des deux est compromis. Même commande de génération que `SECRET`. Fallback sur `SECRET` si absent (rétrocompatibilité).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ALLOWED_ORIGINS
|
||||||
|
|
||||||
|
**Fichier concerné :** `src/createApp.js`
|
||||||
|
**Utilisé par :** CORS
|
||||||
|
|
||||||
|
Liste d'origines autorisées, séparées par des virgules. Si absente ou vide, CORS refuse toutes les origines cross-domain.
|
||||||
|
|
||||||
|
```
|
||||||
|
ALLOWED_ORIGINS=http://localhost:4200,http://localhost:4300
|
||||||
|
```
|
||||||
|
|
||||||
|
En production, indiquer uniquement le domaine du frontend (ex: `https://adastra.example.com`).
|
||||||
Generated
+62
-3
@@ -18,7 +18,10 @@
|
|||||||
"errorhandler": "^1.5.1",
|
"errorhandler": "^1.5.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-jwt": "^8.4.1",
|
"express-jwt": "^8.4.1",
|
||||||
|
"express-rate-limit": "^8.4.1",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"express-validator": "^7.3.2",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"method-override": "3.0.0",
|
"method-override": "3.0.0",
|
||||||
@@ -5005,6 +5008,33 @@
|
|||||||
"node": ">= 8.0.0"
|
"node": ">= 8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express-rate-limit": {
|
||||||
|
"version": "8.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz",
|
||||||
|
"integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ip-address": "10.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/express-rate-limit"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": ">= 4.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-rate-limit/node_modules/ip-address": {
|
||||||
|
"version": "10.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
|
||||||
|
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/express-session": {
|
"node_modules/express-session": {
|
||||||
"version": "1.18.0",
|
"version": "1.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
|
||||||
@@ -5041,6 +5071,25 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz",
|
||||||
"integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ=="
|
"integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/express-validator": {
|
||||||
|
"version": "7.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.2.tgz",
|
||||||
|
"integrity": "sha512-ctLw1Vl6dXVH62dIQMDdTAQkrh480mkFuG6/SGXOaVlwPNukhRAe7EgJIMJ2TSAni8iwHBRp530zAZE5ZPF2IA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": "^4.18.1",
|
||||||
|
"validator": "~13.15.23"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express-validator/node_modules/lodash": {
|
||||||
|
"version": "4.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||||
|
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/express/node_modules/body-parser": {
|
"node_modules/express/node_modules/body-parser": {
|
||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||||
@@ -5750,6 +5799,15 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/helmet": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
@@ -10450,9 +10508,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/validator": {
|
"node_modules/validator": {
|
||||||
"version": "13.15.15",
|
"version": "13.15.35",
|
||||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz",
|
||||||
"integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
|
"integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,10 @@
|
|||||||
"errorhandler": "^1.5.1",
|
"errorhandler": "^1.5.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-jwt": "^8.4.1",
|
"express-jwt": "^8.4.1",
|
||||||
|
"express-rate-limit": "^8.4.1",
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
|
"express-validator": "^7.3.2",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
"js-yaml": "^4.1.1",
|
"js-yaml": "^4.1.1",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"method-override": "3.0.0",
|
"method-override": "3.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user