@lab-anssi/lib 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/cms/adaptateurCmsCrisp.d.ts +1 -4
- package/dist/cms/cmsCrisp.d.ts +3 -21
- package/dist/cms/cmsCrisp.js +1 -1
- package/dist/cms/crispMarkdown.d.ts +2 -1
- package/dist/cms/types.d.ts +22 -0
- package/dist/cms/types.js +2 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/serveur/adaptateurEnvironnementServeurLab.d.ts +2 -0
- package/dist/serveur/adaptateurEnvironnementServeurLab.js +34 -0
- package/dist/serveur/serveurLab.d.ts +9 -0
- package/dist/serveur/serveurLab.js +48 -0
- package/package.json +10 -2
package/README.md
CHANGED
@@ -1,2 +1,43 @@
|
|
1
1
|
# lab-anssi-lib
|
2
2
|
Une librairie de code mutualisé pour les produits du Lab. Innovation de l'ANSSI
|
3
|
+
|
4
|
+
## serveur
|
5
|
+
|
6
|
+
Un certain nombre de comportements standards sont intégrés dans `serveur/serveurLab`, en particulier
|
7
|
+
le filtrage IP (pour n'autoriser que les requêtes venant du waf) et le rate limit.
|
8
|
+
|
9
|
+
Pour l'utiliser, remplacez votre code de la forme :
|
10
|
+
|
11
|
+
```
|
12
|
+
const app = express();
|
13
|
+
app.get('/', (req, res) => {res.json({})});
|
14
|
+
```
|
15
|
+
|
16
|
+
Par un code de la forme :
|
17
|
+
|
18
|
+
```
|
19
|
+
const app = creeServeurLab({
|
20
|
+
reseau: {
|
21
|
+
ipAutorisees: [ipWaf],
|
22
|
+
trustProxy: 0,
|
23
|
+
maxRequetesParMinute: 500,
|
24
|
+
},
|
25
|
+
});
|
26
|
+
app.get('/', (req, res) => {res.json({})});
|
27
|
+
```
|
28
|
+
|
29
|
+
La configuration serveur peut-être construite automatiquement à partir des variables d'environnement :
|
30
|
+
|
31
|
+
```
|
32
|
+
const config = adaptateurEnvironnementServeurLab();
|
33
|
+
const app = creeServeurLab(config);
|
34
|
+
app.get('/', (req, res) => {res.json({})});
|
35
|
+
```
|
36
|
+
|
37
|
+
Dans ce cas, vous devez positionner des variables d'environnement de la forme :
|
38
|
+
|
39
|
+
```
|
40
|
+
SERVEUR_TRUST_PROXY = # optionnel (0 par défaut) nombre de proxies en amont du service ou configuration plus fine de trust proxy, Cf. https://expressjs.com/en/guide/behind-proxies.html
|
41
|
+
SERVEUR_MAX_REQUETES_PAR_MINUTE = # optionnel (600 par défaut) nombre maximum de requêtes par minute par IP
|
42
|
+
SERVEUR_ADRESSES_IP_AUTORISEES = # Seules ces IP seront autorisées. Les autres ne seront pas servies. Séparées par des ',' s'il y en a plusieurs. Supprimer la variable d'env pour désactiver le filtrage.
|
43
|
+
```
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { SectionCrisp } from './types';
|
1
2
|
export type ArticleMarkdownCrisp = {
|
2
3
|
titre: string;
|
3
4
|
contenuMarkdown: string;
|
@@ -12,10 +13,6 @@ export type ResumeArticleCrisp = {
|
|
12
13
|
nom?: string;
|
13
14
|
};
|
14
15
|
};
|
15
|
-
export type SectionCrisp = {
|
16
|
-
id: string;
|
17
|
-
nom: string;
|
18
|
-
};
|
19
16
|
export declare class AdaptateurCmsCrisp {
|
20
17
|
readonly urlBase: string;
|
21
18
|
readonly enteteCrisp: {
|
package/dist/cms/cmsCrisp.d.ts
CHANGED
@@ -1,31 +1,13 @@
|
|
1
|
-
import { AdaptateurCmsCrisp
|
1
|
+
import { AdaptateurCmsCrisp } from './adaptateurCmsCrisp';
|
2
2
|
import CrispMarkdown from './crispMarkdown';
|
3
|
-
|
4
|
-
titre: string;
|
5
|
-
contenu: string | null;
|
6
|
-
description: string;
|
7
|
-
tableDesMatieres: any[];
|
8
|
-
};
|
9
|
-
type ArticleMarkdownCrispAvecSection = {
|
10
|
-
titre: string;
|
11
|
-
contenuMarkdown: string;
|
12
|
-
description: string;
|
13
|
-
section: {
|
14
|
-
id?: string;
|
15
|
-
nom?: string;
|
16
|
-
};
|
17
|
-
};
|
18
|
-
type ResumeArticleCrispAvecSlug = ResumeArticleCrisp & {
|
19
|
-
slug: string | null;
|
20
|
-
};
|
3
|
+
import { ArticleCrispAvecSection, PageHtmlCrisp, ResumeArticleCrispAvecSlug, SectionCrisp } from './types';
|
21
4
|
export declare class CmsCrisp {
|
22
5
|
adaptateurCmsCrisp: AdaptateurCmsCrisp;
|
23
6
|
constructeurCrispMarkdown: (contenuMarkdown: string) => CrispMarkdown;
|
24
7
|
constructor(idSite: string, cleApi: string);
|
25
8
|
recupereArticle(id: string): Promise<PageHtmlCrisp>;
|
26
|
-
recupereArticleCategorie(slug: string, idCategorie: string): Promise<
|
9
|
+
recupereArticleCategorie(slug: string, idCategorie: string): Promise<ArticleCrispAvecSection>;
|
27
10
|
recupereSectionsCategorie(idCategorie: string): Promise<SectionCrisp[]>;
|
28
11
|
recupereArticlesCategorie(idCategorie: string): Promise<ResumeArticleCrispAvecSlug[]>;
|
29
12
|
private extraitSlugArticle;
|
30
13
|
}
|
31
|
-
export {};
|
package/dist/cms/cmsCrisp.js
CHANGED
@@ -31,7 +31,7 @@ class CmsCrisp {
|
|
31
31
|
if (!article) {
|
32
32
|
throw new erreurs_1.ErreurArticleCrispIntrouvable();
|
33
33
|
}
|
34
|
-
const articleCrisp = await this.
|
34
|
+
const articleCrisp = await this.recupereArticle(article.id);
|
35
35
|
return {
|
36
36
|
...articleCrisp,
|
37
37
|
section: { id: article.section.id, nom: article.section.nom },
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import { EntreeTableDesMatieres } from './types';
|
1
2
|
declare class CrispMarkdown {
|
2
3
|
private contenuMarkdown;
|
3
4
|
private contenuHTML;
|
@@ -7,6 +8,6 @@ declare class CrispMarkdown {
|
|
7
8
|
constructor(contenuMarkdown: string);
|
8
9
|
parseLeMarkdown(): void;
|
9
10
|
versHTML(): string | null;
|
10
|
-
tableDesMatieres():
|
11
|
+
tableDesMatieres(): EntreeTableDesMatieres[];
|
11
12
|
}
|
12
13
|
export default CrispMarkdown;
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import { ResumeArticleCrisp } from './adaptateurCmsCrisp';
|
2
|
+
export type EntreeTableDesMatieres = {
|
3
|
+
id: string;
|
4
|
+
texte: string;
|
5
|
+
profondeur: number;
|
6
|
+
};
|
7
|
+
export type PageHtmlCrisp = {
|
8
|
+
titre: string;
|
9
|
+
contenu: string | null;
|
10
|
+
description: string;
|
11
|
+
tableDesMatieres: EntreeTableDesMatieres[];
|
12
|
+
};
|
13
|
+
export type ResumeArticleCrispAvecSlug = ResumeArticleCrisp & {
|
14
|
+
slug: string | null;
|
15
|
+
};
|
16
|
+
export type SectionCrisp = {
|
17
|
+
id: string;
|
18
|
+
nom: string;
|
19
|
+
};
|
20
|
+
export type ArticleCrispAvecSection = PageHtmlCrisp & {
|
21
|
+
section: Partial<SectionCrisp>;
|
22
|
+
};
|
package/dist/index.d.ts
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
-
export {
|
1
|
+
export { type EntreeTableDesMatieres, type PageHtmlCrisp, type ResumeArticleCrispAvecSlug, type SectionCrisp, type ArticleCrispAvecSection, } from './cms/types';
|
2
|
+
export { CmsCrisp } from './cms/cmsCrisp';
|
2
3
|
export { AdaptateurProfilAnssi } from './profilAnssi/adaptateurProfilAnssi';
|
3
4
|
export { ErreurArticleCrispIntrouvable } from './erreurs';
|
5
|
+
export { type ConfigurationServeurLab } from './serveur/serveurLab';
|
6
|
+
export { creeServeurLab } from './serveur/serveurLab';
|
7
|
+
export { adaptateurEnvironnementServeurLab } from './serveur/adaptateurEnvironnementServeurLab';
|
package/dist/index.js
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.ErreurArticleCrispIntrouvable = exports.AdaptateurProfilAnssi = exports.CmsCrisp = void 0;
|
3
|
+
exports.adaptateurEnvironnementServeurLab = exports.creeServeurLab = exports.ErreurArticleCrispIntrouvable = exports.AdaptateurProfilAnssi = exports.CmsCrisp = void 0;
|
4
4
|
var cmsCrisp_1 = require("./cms/cmsCrisp");
|
5
5
|
Object.defineProperty(exports, "CmsCrisp", { enumerable: true, get: function () { return cmsCrisp_1.CmsCrisp; } });
|
6
6
|
var adaptateurProfilAnssi_1 = require("./profilAnssi/adaptateurProfilAnssi");
|
7
7
|
Object.defineProperty(exports, "AdaptateurProfilAnssi", { enumerable: true, get: function () { return adaptateurProfilAnssi_1.AdaptateurProfilAnssi; } });
|
8
8
|
var erreurs_1 = require("./erreurs");
|
9
9
|
Object.defineProperty(exports, "ErreurArticleCrispIntrouvable", { enumerable: true, get: function () { return erreurs_1.ErreurArticleCrispIntrouvable; } });
|
10
|
+
var serveurLab_1 = require("./serveur/serveurLab");
|
11
|
+
Object.defineProperty(exports, "creeServeurLab", { enumerable: true, get: function () { return serveurLab_1.creeServeurLab; } });
|
12
|
+
var adaptateurEnvironnementServeurLab_1 = require("./serveur/adaptateurEnvironnementServeurLab");
|
13
|
+
Object.defineProperty(exports, "adaptateurEnvironnementServeurLab", { enumerable: true, get: function () { return adaptateurEnvironnementServeurLab_1.adaptateurEnvironnementServeurLab; } });
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.adaptateurEnvironnementServeurLab = void 0;
|
4
|
+
const trustProxy = () => {
|
5
|
+
const trustProxyEnChaine = process.env.SERVEUR_TRUST_PROXY || '0';
|
6
|
+
const trustProxyEnNombre = Number(trustProxyEnChaine);
|
7
|
+
if (isNaN(trustProxyEnNombre)) {
|
8
|
+
console.warn(`Attention ! SERVEUR_TRUST_PROXY positionné à ${trustProxyEnChaine}`);
|
9
|
+
return trustProxyEnChaine;
|
10
|
+
}
|
11
|
+
else {
|
12
|
+
return trustProxyEnNombre;
|
13
|
+
}
|
14
|
+
};
|
15
|
+
const maxRequetesParMinute = () => {
|
16
|
+
const maxEnChaine = process.env.SERVEUR_MAX_REQUETES_PAR_MINUTE || '600';
|
17
|
+
const maxEnNombre = Number(maxEnChaine);
|
18
|
+
if (isNaN(maxEnNombre)) {
|
19
|
+
throw new Error(`SERVEUR_MAX_REQUETES_PAR_MINUTE n'est pas un nombre : ${maxEnChaine}`);
|
20
|
+
}
|
21
|
+
else {
|
22
|
+
return maxEnNombre;
|
23
|
+
}
|
24
|
+
};
|
25
|
+
const adaptateurEnvironnementServeurLab = () => {
|
26
|
+
return {
|
27
|
+
reseau: {
|
28
|
+
trustProxy: trustProxy(),
|
29
|
+
maxRequetesParMinute: maxRequetesParMinute(),
|
30
|
+
ipAutorisees: process.env.SERVEUR_ADRESSES_IP_AUTORISEES?.split(',') ?? false,
|
31
|
+
},
|
32
|
+
};
|
33
|
+
};
|
34
|
+
exports.adaptateurEnvironnementServeurLab = adaptateurEnvironnementServeurLab;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { Express } from 'express';
|
2
|
+
export declare const creeServeurLab: (config: ConfigurationServeurLab) => Express;
|
3
|
+
export type ConfigurationServeurLab = {
|
4
|
+
reseau: {
|
5
|
+
trustProxy: number | string;
|
6
|
+
maxRequetesParMinute: number;
|
7
|
+
ipAutorisees: string[] | false;
|
8
|
+
};
|
9
|
+
};
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
exports.creeServeurLab = void 0;
|
7
|
+
const express_ipfilter_1 = require("express-ipfilter");
|
8
|
+
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
|
9
|
+
const creeServeurLab = (config) => {
|
10
|
+
const express = require('express');
|
11
|
+
const app = express();
|
12
|
+
const limiteRequetesParMinute = (0, express_rate_limit_1.default)({
|
13
|
+
windowMs: 60 * 1000,
|
14
|
+
limit: config.reseau.maxRequetesParMinute,
|
15
|
+
});
|
16
|
+
app.set('trust proxy', config.reseau.trustProxy);
|
17
|
+
app.use(limiteRequetesParMinute);
|
18
|
+
if (config.reseau.ipAutorisees) {
|
19
|
+
app.use((0, express_ipfilter_1.IpFilter)(config.reseau.ipAutorisees, {
|
20
|
+
detectIp: (request) => {
|
21
|
+
const forwardedFor = request.headers['x-forwarded-for'];
|
22
|
+
if (typeof forwardedFor === 'string') {
|
23
|
+
const ips = forwardedFor
|
24
|
+
.split(',')
|
25
|
+
.map((ip) => ip.trim())
|
26
|
+
.filter((ip) => ip !== '');
|
27
|
+
if (ips.length > 0) {
|
28
|
+
const ipWaf = ips[ips.length - 1];
|
29
|
+
return ipWaf;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
return 'interdire';
|
33
|
+
},
|
34
|
+
mode: 'allow',
|
35
|
+
log: false,
|
36
|
+
}));
|
37
|
+
app.use((err, req, res, next) => {
|
38
|
+
if (err instanceof express_ipfilter_1.IpDeniedError) {
|
39
|
+
res.status(403).send('Access denied');
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
next(err);
|
43
|
+
}
|
44
|
+
});
|
45
|
+
}
|
46
|
+
return app;
|
47
|
+
};
|
48
|
+
exports.creeServeurLab = creeServeurLab;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lab-anssi/lib",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.5.0",
|
4
4
|
"repository": {
|
5
5
|
"type": "git",
|
6
6
|
"url": "git+https://github.com/betagouv/lab-anssi-lib.git"
|
@@ -26,14 +26,22 @@
|
|
26
26
|
}
|
27
27
|
},
|
28
28
|
"devDependencies": {
|
29
|
+
"@types/express": "5.0.0",
|
29
30
|
"@types/node": "^22.14.1",
|
31
|
+
"@types/supertest": "^6.0.3",
|
30
32
|
"prettier": "^3.5.3",
|
33
|
+
"supertest": "^7.1.1",
|
31
34
|
"tsx": "^4.19.3",
|
32
35
|
"typescript": "^5.8.3"
|
33
36
|
},
|
34
37
|
"dependencies": {
|
38
|
+
"express-ipfilter": "^1.3.2",
|
39
|
+
"express-rate-limit": "^7.5.1",
|
35
40
|
"axios": "^1.9.0",
|
36
41
|
"html-entities": "^2.6.0",
|
37
42
|
"marked": "^13.0.3"
|
43
|
+
},
|
44
|
+
"peerDependencies": {
|
45
|
+
"express": "^4.21.2"
|
38
46
|
}
|
39
|
-
}
|
47
|
+
}
|