@zarex/fastify 0.1.0 → 0.1.1
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 +158 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# @zarex/fastify
|
|
2
|
+
|
|
3
|
+
Hook Zarex pour Fastify — protège votre application contre les bots, les abus de trafic, les injections (SQL, XSS, SSRF...) et les fuites de données sensibles, en quelques lignes.
|
|
4
|
+
|
|
5
|
+
Compatible Fastify 4 et 5 (aucune dépendance à `fastify` à l'exécution). Fonctionne aussi avec NestJS (plateforme Fastify).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @zarex/fastify
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Démarrage rapide
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import Fastify from "fastify";
|
|
17
|
+
import { zarexFastify, shield, rateLimit } from "@zarex/fastify";
|
|
18
|
+
|
|
19
|
+
const app = Fastify();
|
|
20
|
+
|
|
21
|
+
app.addHook(
|
|
22
|
+
"onRequest",
|
|
23
|
+
zarexFastify({
|
|
24
|
+
key: process.env.ZAREX_KEY!,
|
|
25
|
+
rules: [shield(), rateLimit({ max: 100, window: "1m" })],
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
app.get("/", async () => ({ ok: true }));
|
|
30
|
+
app.listen({ port: 3000 });
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Une requête refusée reçoit automatiquement `403 Forbidden` (ou `429 Too Many Requests` avec l'en-tête `retry-after` pour une limite de débit) — sans aucun code supplémentaire.
|
|
34
|
+
|
|
35
|
+
**Fail-open** : si Zarex est injoignable, lent ou répond de façon inattendue, la requête passe toujours. Une panne chez Zarex ne doit jamais faire tomber votre application.
|
|
36
|
+
|
|
37
|
+
### Avec NestJS (plateforme Fastify)
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import type { FastifyInstance } from "fastify";
|
|
41
|
+
(app.getHttpAdapter().getInstance() as FastifyInstance).addHook(
|
|
42
|
+
"onRequest",
|
|
43
|
+
zarexFastify({ key, rules: [shield()] })
|
|
44
|
+
);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## L'adresse IP du visiteur
|
|
48
|
+
|
|
49
|
+
Par défaut (`ipSource` non renseigné), l'adaptateur utilise `request.ip`, calculé par Fastify selon l'option [`trustProxy`](https://fastify.dev/docs/latest/Reference/Server/#trustproxy) de votre serveur :
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const app = Fastify({ trustProxy: 1 }); // 1 proxy de confiance devant Fastify
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Sans `trustProxy`, Fastify ignore `X-Forwarded-For` — configurez-le pour correspondre à **votre** infrastructure réelle, sinon tous vos visiteurs apparaissent sous la même adresse IP et toute règle de limite de débit basée sur l'IP devient inutile.
|
|
56
|
+
|
|
57
|
+
## Options
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
zarexFastify({
|
|
61
|
+
key: string, // obligatoire : clé API du projet
|
|
62
|
+
rules: Rule[], // obligatoire : 1 à 10 règles, voir ci-dessous
|
|
63
|
+
ipSource?: IpSource, // défaut "framework" (voir ci-dessus)
|
|
64
|
+
inspectBody?: boolean, // défaut false — voir "Inspecter le corps"
|
|
65
|
+
secureHeaders?: boolean | SecureHeadersOptions,
|
|
66
|
+
responseGuard?: boolean | ResponseGuardOptions, // voir zarexFastifyOnSend ci-dessous
|
|
67
|
+
onDeny?: (req, reply, decision) => void,
|
|
68
|
+
onDegraded?: (req, decision) => void,
|
|
69
|
+
call?: (req) => ProtectCall | undefined,
|
|
70
|
+
timeoutMs?: number, // défaut 500 ms
|
|
71
|
+
breaker?: BreakerOptions | false,
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Inspecter le corps de la requête (`inspectBody`) — attention au hook choisi
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// ⚠️ "onRequest" : Fastify n'a PAS ENCORE lu le corps à ce stade — inspectBody ne verrait jamais rien.
|
|
79
|
+
app.addHook("onRequest", zarexFastify({ ..., inspectBody: true })); // piège
|
|
80
|
+
|
|
81
|
+
// ✅ "preHandler" : le corps est déjà disponible.
|
|
82
|
+
app.addHook("preHandler", zarexFastify({ ..., inspectBody: true }));
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Si vous enregistrez le hook en `onRequest` avec `inspectBody: true` alors qu'une requête a manifestement un corps (`Content-Length` > 0), un avertissement unique s'affiche dans la console (`console.warn`) pour éviter que cette erreur passe inaperçue.
|
|
86
|
+
|
|
87
|
+
### En-têtes de sécurité (`secureHeaders`)
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
app.addHook("onRequest", zarexFastify({ ..., secureHeaders: true }));
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Ajoutés à **toutes** les réponses, y compris les refus. `true` applique des défauts prudents (HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy...). Retire aussi `x-powered-by`.
|
|
94
|
+
|
|
95
|
+
### Surveillance des réponses (`responseGuard`) — un hook séparé
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
app.addHook("onSend", zarexFastifyOnSend({ responseGuard: true }));
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Contrairement aux autres adaptateurs, Fastify demande d'enregistrer un **second hook** (`onSend`) pour surveiller les réponses de votre application et masquer (ou bloquer) les cartes bancaires, clés d'API, jetons JWT, mots de passe... qui s'y retrouveraient par erreur. Les flux (streams) ne sont pas analysés.
|
|
102
|
+
|
|
103
|
+
### Identifiants personnalisés (`call`)
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
app.addHook(
|
|
107
|
+
"preHandler",
|
|
108
|
+
zarexFastify({
|
|
109
|
+
key,
|
|
110
|
+
rules: [rateLimit({ max: 100, window: "1m", characteristics: ["userId"] })],
|
|
111
|
+
call: (req) => ({ characteristics: { userId: (req as any).user?.id } }),
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Les valeurs sont hachées en SHA-256 avant l'envoi à Zarex. Une erreur dans cette fonction est silencieusement ignorée. `call` peut aussi fournir `tokensRequested`, `email`/`phone` ou `text` (voir [`@zarex/core`](https://www.npmjs.com/package/@zarex/core)).
|
|
117
|
+
|
|
118
|
+
### Lire le résultat de la décision
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
app.get("/", async (req) => {
|
|
122
|
+
console.log(req.zarex?.verdict); // "ALLOW" | "DENY" | "ERROR"
|
|
123
|
+
return { ok: true };
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Règles disponibles
|
|
128
|
+
|
|
129
|
+
| Fonction | Résumé |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `shield(opts?)` | Pare-feu applicatif : injection SQL, XSS, traversée de répertoires, SSRF, SSTI... |
|
|
132
|
+
| `rateLimit({ max, window, ... })` | Limite de requêtes par visiteur sur une fenêtre de temps. |
|
|
133
|
+
| `tokenBucket({ capacity, refillRate, interval })` | Budget de jetons avec pics autorisés. |
|
|
134
|
+
| `detectBot(opts?)` | Détecte les robots connus, avec vérification DNS inversée. |
|
|
135
|
+
| `requestFilter({ expressions, logic })` | Règles personnalisées sur IP, chemin, en-têtes, cookies, pays... |
|
|
136
|
+
| `sensitiveInfo(opts?)` | Refuse une requête contenant une donnée sensible (nécessite `inspectBody`). |
|
|
137
|
+
| `emailValidation(opts?)` | Valide un email donné via `call: () => ({ email })`. |
|
|
138
|
+
| `phoneValidation(opts?)` | Valide un numéro donné, orienté opérateurs africains (MTN, Moov, Orange, Wave...). |
|
|
139
|
+
| `promptInjection(opts?)` | Détecte les tentatives de manipulation d'un assistant IA, via `call: () => ({ text })`. |
|
|
140
|
+
|
|
141
|
+
Chaque règle accepte `mode: "LIVE" | "DRY_RUN" | "OFF"`. Détails complets des options : voir [`@zarex/core`](https://www.npmjs.com/package/@zarex/core).
|
|
142
|
+
|
|
143
|
+
## Réponse de refus personnalisée (`onDeny`)
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
zarexFastify({
|
|
147
|
+
...,
|
|
148
|
+
onDeny: (req, reply, decision) => {
|
|
149
|
+
reply.code(403).send({ message: "Accès refusé", id: decision.id });
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Sans `onDeny`, la réponse par défaut est `403` (ou `429` + `retry-after`), au format `{ error: { code, message, request_id } }`.
|
|
155
|
+
|
|
156
|
+
## Licence
|
|
157
|
+
|
|
158
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zarex/fastify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Zarex — Fastify (et NestJS avec Fastify)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
],
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@zarex/core": "^0.1.
|
|
30
|
+
"@zarex/core": "^0.1.1"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"fastify": ">=4"
|