@nodefony/http 10.0.0-alpha.9 → 10.0.0-beta.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 +7 -0
- package/dist/index.js +4 -2
- package/dist/nodefony/command/assetsPublishCommand.js +1 -1
- package/dist/nodefony/command/certificatesCommand.js +1 -1
- package/dist/nodefony/command/networkCommand.js +1 -1
- package/dist/nodefony/command/proxyGenerateCommand.js +1 -1
- package/dist/nodefony/config/config.js +22 -1
- package/dist/nodefony/interfaces/IRouting.js +1 -0
- package/dist/nodefony/interfaces/ISecurity.js +1 -0
- package/dist/nodefony/service/ProfilerAdminApi.js +2 -1
- package/dist/nodefony/service/audit-logger.js +16 -13
- package/dist/nodefony/service/certificates.js +25 -13
- package/dist/nodefony/service/error-renderer.js +11 -8
- package/dist/nodefony/service/http-kernel.js +340 -163
- package/dist/nodefony/service/pretty-request-logger.js +3 -10
- package/dist/nodefony/service/servers/clientError.js +2 -2
- package/dist/nodefony/service/servers/server-http.js +20 -20
- package/dist/nodefony/service/servers/server-https.js +38 -39
- package/dist/nodefony/service/servers/server-static.js +13 -17
- package/dist/nodefony/service/servers/server-websocket-secure.js +8 -7
- package/dist/nodefony/service/servers/server-websocket.js +8 -7
- package/dist/nodefony/service/servers/wsHeartbeat.js +2 -2
- package/dist/nodefony/service/sessions/sessions-service.js +19 -12
- package/dist/nodefony/service/trace.js +1 -1
- package/dist/nodefony/service/upload/upload-service.js +8 -7
- package/dist/nodefony/src/assets/prebuiltUi.js +1 -1
- package/dist/nodefony/src/context/Context.js +34 -19
- package/dist/nodefony/src/context/forwarded.js +4 -4
- package/dist/nodefony/src/context/http/HttpContext.js +133 -88
- package/dist/nodefony/src/context/http/Request.js +93 -44
- package/dist/nodefony/src/context/http/Response.js +107 -51
- package/dist/nodefony/src/context/http/parser.js +9 -10
- package/dist/nodefony/src/context/http/urlFastPath.js +1 -2
- package/dist/nodefony/src/context/http2/Response.js +53 -25
- package/dist/nodefony/src/context/metaData.js +1 -1
- package/dist/nodefony/src/context/trustProxy.js +4 -3
- package/dist/nodefony/src/context/websocket/Response.js +13 -6
- package/dist/nodefony/src/context/websocket/WebsocketContext.js +59 -26
- package/dist/nodefony/src/context/websocket/wsCloseReason.js +41 -0
- package/dist/nodefony/src/cookies/cookie.js +3 -3
- package/dist/nodefony/src/errors/httpError.js +6 -6
- package/dist/nodefony/src/errors/thrownMessage.js +27 -0
- package/dist/nodefony/src/profiler/FrameProfile.js +4 -3
- package/dist/nodefony/src/profiler/Profiler.js +4 -4
- package/dist/nodefony/src/servers/kernelServers.js +29 -0
- package/dist/nodefony/src/servers/portBinder.js +8 -21
- package/dist/nodefony/src/session/session.js +18 -10
- package/dist/nodefony/src/session/sessionStoreFailure.js +2 -1
- package/dist/nodefony/src/session/storage/RevocationGuardStorage.js +24 -9
- package/dist/types/index.d.ts +16 -0
- package/dist/types/nodefony/config/config.d.ts +22 -0
- package/dist/types/nodefony/interfaces/IContext.d.ts +24 -7
- package/dist/types/nodefony/interfaces/ICookie.d.ts +18 -18
- package/dist/types/nodefony/interfaces/IHttpKernel.d.ts +10 -4
- package/dist/types/nodefony/interfaces/IResponse.d.ts +1 -1
- package/dist/types/nodefony/interfaces/IRouting.d.ts +87 -0
- package/dist/types/nodefony/interfaces/ISecurity.d.ts +45 -0
- package/dist/types/nodefony/interfaces/ISession.d.ts +8 -8
- package/dist/types/nodefony/interfaces/IUpload.d.ts +9 -9
- package/dist/types/nodefony/interfaces/index.d.ts +2 -0
- package/dist/types/nodefony/service/audit-logger.d.ts +14 -10
- package/dist/types/nodefony/service/certificates.d.ts +10 -10
- package/dist/types/nodefony/service/http-kernel.d.ts +192 -42
- package/dist/types/nodefony/service/servers/server-http.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-https.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-websocket-secure.d.ts +4 -1
- package/dist/types/nodefony/service/servers/server-websocket.d.ts +4 -1
- package/dist/types/nodefony/service/sessions/sessions-service.d.ts +6 -2
- package/dist/types/nodefony/service/upload/upload-service.d.ts +1 -1
- package/dist/types/nodefony/src/context/Context.d.ts +21 -10
- package/dist/types/nodefony/src/context/forwarded.d.ts +3 -3
- package/dist/types/nodefony/src/context/http/HttpContext.d.ts +75 -18
- package/dist/types/nodefony/src/context/http/Request.d.ts +39 -2
- package/dist/types/nodefony/src/context/http/Response.d.ts +47 -7
- package/dist/types/nodefony/src/context/http/parser.d.ts +1 -1
- package/dist/types/nodefony/src/context/http2/Response.d.ts +32 -2
- package/dist/types/nodefony/src/context/websocket/Response.d.ts +4 -4
- package/dist/types/nodefony/src/context/websocket/WebsocketContext.d.ts +11 -2
- package/dist/types/nodefony/src/context/websocket/wsCloseReason.d.ts +21 -0
- package/dist/types/nodefony/src/cookies/cookie.d.ts +9 -9
- package/dist/types/nodefony/src/errors/httpError.d.ts +7 -7
- package/dist/types/nodefony/src/errors/thrownMessage.d.ts +12 -0
- package/dist/types/nodefony/src/profiler/Profiler.d.ts +28 -28
- package/dist/types/nodefony/src/servers/kernelServers.d.ts +20 -0
- package/dist/types/nodefony/src/servers/portBinder.d.ts +6 -19
- package/dist/types/nodefony/src/session/session.d.ts +12 -12
- package/dist/types/nodefony/src/session/storage/RevocationGuardStorage.d.ts +1 -1
- package/docs/cookies.md +20 -20
- package/docs/observabilite.md +25 -25
- package/docs/rate-limit.md +18 -18
- package/docs/servers.md +52 -52
- package/docs/session.md +65 -60
- package/docs/upload.md +36 -30
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -34,6 +34,13 @@ serveur → http-kernel.handle() → rate-limit IP → createContext()
|
|
|
34
34
|
Chaque contexte (HTTP + WS) porte un `requestId` (UUID v4, ou `X-Request-Id` client),
|
|
35
35
|
réinjecté dans la réponse et corrélé dans les logs via AsyncLocalStorage.
|
|
36
36
|
|
|
37
|
+
Chaque requête HTTP — et chaque connexion WebSocket — reçoit aussi **son propre conteneur de
|
|
38
|
+
services** : un scope ouvert à l'entrée (`enterScope`), dont le prototype est le conteneur du kernel,
|
|
39
|
+
refermé après les hooks `onAfterResponse`/`onFinish` (`leaveScope`), y compris quand un hook lève.
|
|
40
|
+
Le serveur le pose dans le contexte asynchrone de la requête : `RequestContext.getScope()` le rend
|
|
41
|
+
depuis n'importe quel code, et les services `@injectable({ scope: "request" })` y vivent. Détail :
|
|
42
|
+
[Injection & portées](https://github.com/nodefony/nodefony-core/blob/main/docs/architecture/injection-portees.md).
|
|
43
|
+
|
|
37
44
|
## Rate-limit général par IP
|
|
38
45
|
|
|
39
46
|
Plafond de trafic **par IP cliente** sur toutes les routes HTTP — protège la capacité du serveur
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import config, { httpConfigSchema } from "./nodefony/config/config.js";
|
|
1
|
+
import config, { httpConfigSchema, httpOverlaySchema } from "./nodefony/config/config.js";
|
|
2
2
|
import { defineHttpConfig, httpConfigJsonSchema } from "./nodefony/config/defineModuleConfig.js";
|
|
3
3
|
import { createHttpAdminApi } from "./nodefony/service/HttpAdminApi.js";
|
|
4
4
|
import { createProfilerAdminApi } from "./nodefony/service/ProfilerAdminApi.js";
|
|
@@ -42,6 +42,7 @@ import { PrebuiltUi, resolveUiDelivery } from "./nodefony/src/assets/prebuiltUi.
|
|
|
42
42
|
import { Kernel, Module, services } from "nodefony";
|
|
43
43
|
//#region index.ts
|
|
44
44
|
let Http = class Http extends Module {
|
|
45
|
+
overlaySchema = httpOverlaySchema;
|
|
45
46
|
constructor(kernel) {
|
|
46
47
|
super("http", kernel, import.meta.url, config);
|
|
47
48
|
this.addCommand(Network);
|
|
@@ -68,7 +69,8 @@ let Http = class Http extends Module {
|
|
|
68
69
|
* certificats `serialNumber`) — cf `defineHttpConfig`.
|
|
69
70
|
*/
|
|
70
71
|
async onKernelRegister() {
|
|
71
|
-
|
|
72
|
+
const input = this.options;
|
|
73
|
+
this.options = defineHttpConfig(input ?? {}, this.kernel);
|
|
72
74
|
return this;
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
@@ -80,7 +80,7 @@ var AssetsPublish = class extends Command {
|
|
|
80
80
|
}
|
|
81
81
|
/** Sources = mounts natifs (`server-static`) + bundles `@nodefony/frontend`. */
|
|
82
82
|
collectSources() {
|
|
83
|
-
const modules = this.kernel?.getModules
|
|
83
|
+
const modules = this.kernel?.getModules() ?? {};
|
|
84
84
|
const sources = [];
|
|
85
85
|
const staticSvc = modules["http"]?.get("server-static");
|
|
86
86
|
staticSvc?.mountModulePublics?.();
|
|
@@ -21,7 +21,7 @@ var Certificates = class extends Command {
|
|
|
21
21
|
this.addOption("-j, --json", "output the certificate summary as JSON");
|
|
22
22
|
}
|
|
23
23
|
async generate(opts) {
|
|
24
|
-
const service = (this.kernel?.getModules()
|
|
24
|
+
const service = (this.kernel?.getModules()["http"])?.get("certificates") ?? null;
|
|
25
25
|
if (!service) {
|
|
26
26
|
this.log("Service 'certificates' introuvable (module @nodefony/http non chargé).", "ERROR");
|
|
27
27
|
return this;
|
|
@@ -16,7 +16,7 @@ var Network = class extends Command {
|
|
|
16
16
|
let network = this.kernel?.getNetwork();
|
|
17
17
|
let result = {};
|
|
18
18
|
if (arg) {
|
|
19
|
-
if (network?.interfaces[arg]) result = network
|
|
19
|
+
if (network?.interfaces[arg]) result = network.interfaces[arg];
|
|
20
20
|
} else result = network?.interfaces;
|
|
21
21
|
if (opts.json) process.stdout.write(`${JSON.stringify(result, void 0, " ")}\n`);
|
|
22
22
|
else process.stdout.write(`${inspect(result, { colors: process.stdout.isTTY })}\n`);
|
|
@@ -43,7 +43,7 @@ var ProxyGenerate = class extends Command {
|
|
|
43
43
|
}
|
|
44
44
|
/** Construit le modèle d'introspection depuis le kernel + le service statique. */
|
|
45
45
|
buildIntrospection(opts) {
|
|
46
|
-
const module = this.kernel?.getModules()
|
|
46
|
+
const module = this.kernel?.getModules()["http"];
|
|
47
47
|
const httpOpts = module?.options ?? {};
|
|
48
48
|
const servers = (this.kernel?.options)?.servers;
|
|
49
49
|
const staticSvc = module?.get("server-static");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { overlayField } from "nodefony";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
//#region nodefony/config/config.ts
|
|
3
4
|
/**
|
|
@@ -327,9 +328,29 @@ const httpConfigSchema = z.strictObject({
|
|
|
327
328
|
})
|
|
328
329
|
}).describe("Configuration de @nodefony/http.");
|
|
329
330
|
/**
|
|
331
|
+
* LISTE BLANCHE des clés qu'une requête peut surcharger pour elle seule
|
|
332
|
+
* (`overlayConfig("@nodefony/http", …)`, #494) : les quotas de corps et
|
|
333
|
+
* d'envoi, lus au MOMENT de lire le corps (`Request.readBodyLimits`) — un
|
|
334
|
+
* calque posé avant la lecture du corps (écouteur `onRequestScope`, organisation
|
|
335
|
+
* reconnue à son nom d'hôte) les voit. Tout le reste est refusé : en-têtes de
|
|
336
|
+
* sécurité, pare-feu, `trustProxy`, sessions, limites de débit, certificats —
|
|
337
|
+
* et `queryString`, lu au constructeur de la requête, avant tout calque.
|
|
338
|
+
* Chaque champ réutilise celui du schéma (mêmes bornes) sans son défaut.
|
|
339
|
+
*/
|
|
340
|
+
const httpOverlaySchema = z.strictObject({
|
|
341
|
+
maxBodySize: overlayField(httpConfigSchema.shape.maxBodySize),
|
|
342
|
+
upload: z.strictObject({
|
|
343
|
+
maxFileSize: overlayField(uploadSchema.shape.maxFileSize),
|
|
344
|
+
maxTotalFileSize: overlayField(uploadSchema.shape.maxTotalFileSize),
|
|
345
|
+
maxFiles: overlayField(uploadSchema.shape.maxFiles),
|
|
346
|
+
maxFields: overlayField(uploadSchema.shape.maxFields),
|
|
347
|
+
maxFieldsSize: overlayField(uploadSchema.shape.maxFieldsSize)
|
|
348
|
+
}).partial()
|
|
349
|
+
}).partial().describe("Clés de @nodefony/http surchargeables par requête.");
|
|
350
|
+
/**
|
|
330
351
|
* Défauts du module, matérialisés depuis le schéma (source unique). Toujours
|
|
331
352
|
* valides par construction ; passés au `super(..., config)` du Module class.
|
|
332
353
|
*/
|
|
333
354
|
const config = httpConfigSchema.parse({});
|
|
334
355
|
//#endregion
|
|
335
|
-
export { config as default, httpConfigSchema };
|
|
356
|
+
export { config as default, httpConfigSchema, httpOverlaySchema };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -51,7 +51,8 @@ function createProfilerAdminApi(profiler) {
|
|
|
51
51
|
path: "{id}",
|
|
52
52
|
summary: "Full profile (phase timeline) for a requestId",
|
|
53
53
|
handler: ({ params }) => {
|
|
54
|
-
const
|
|
54
|
+
const { id = "" } = params;
|
|
55
|
+
const entry = profiler.get(id);
|
|
55
56
|
if (!entry) return {
|
|
56
57
|
status: 404,
|
|
57
58
|
body: {
|
|
@@ -2,6 +2,19 @@ import { RequestContext } from "nodefony";
|
|
|
2
2
|
import { performance } from "node:perf_hooks";
|
|
3
3
|
//#region nodefony/service/audit-logger.ts
|
|
4
4
|
/**
|
|
5
|
+
* Durée totale de la requête, de la première phase (`startMs`) à maintenant.
|
|
6
|
+
* `null` si le chronométrage est coupé (aucune phase enregistrée). Partagée
|
|
7
|
+
* par le journal d'audit et le journal lisible — une seule définition.
|
|
8
|
+
*
|
|
9
|
+
* @param phases - phases chronométrées du contexte
|
|
10
|
+
* @returns durée en millisecondes, ou `null`
|
|
11
|
+
*/
|
|
12
|
+
function computeDurationMs(phases) {
|
|
13
|
+
const first = phases.at(0);
|
|
14
|
+
if (first === void 0 || typeof first.startMs !== "number") return null;
|
|
15
|
+
return performance.now() - first.startMs;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
5
18
|
* Severity derived from HTTP status code — RFC 9110 categories.
|
|
6
19
|
* 1xx/2xx/3xx → INFO ; 4xx → WARNING ; 5xx → ERROR.
|
|
7
20
|
* Unknown/missing status → INFO.
|
|
@@ -77,7 +90,7 @@ var JsonAuditLogger = class {
|
|
|
77
90
|
method: ctx.method,
|
|
78
91
|
url: ctx.url,
|
|
79
92
|
status,
|
|
80
|
-
durationMs:
|
|
93
|
+
durationMs: computeDurationMs(ctx.phases),
|
|
81
94
|
remoteAddress: ctx.remoteAddress ?? null,
|
|
82
95
|
host: ctx.getHost?.() ?? null,
|
|
83
96
|
userAgent: ctx.getUserAgent?.() ?? null,
|
|
@@ -108,7 +121,7 @@ var JsonAuditLogger = class {
|
|
|
108
121
|
method: ctx.method,
|
|
109
122
|
url: ctx.url,
|
|
110
123
|
status,
|
|
111
|
-
durationMs:
|
|
124
|
+
durationMs: computeDurationMs(ctx.phases),
|
|
112
125
|
remoteAddress: ctx.remoteAddress ?? null,
|
|
113
126
|
host: ctx.getHost?.() ?? null,
|
|
114
127
|
userAgent: ctx.getUserAgent?.() ?? null,
|
|
@@ -128,16 +141,6 @@ var JsonAuditLogger = class {
|
|
|
128
141
|
};
|
|
129
142
|
}
|
|
130
143
|
/**
|
|
131
|
-
* Total request duration computed from the first phase startMs to now.
|
|
132
|
-
* Returns null if timing is disabled (no phases recorded).
|
|
133
|
-
*/
|
|
134
|
-
computeDurationMs(phases) {
|
|
135
|
-
if (!phases.length) return null;
|
|
136
|
-
const first = phases[0];
|
|
137
|
-
if (typeof first.startMs !== "number") return null;
|
|
138
|
-
return performance.now() - first.startMs;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
144
|
* Serialise an Error (recursively for `cause` chain) into an AuditErrorEntry.
|
|
142
145
|
* Stack is included only when `includeStack === true` (dev default).
|
|
143
146
|
* Cause chain is capped at `maxCauseDepth`.
|
|
@@ -156,4 +159,4 @@ var JsonAuditLogger = class {
|
|
|
156
159
|
}
|
|
157
160
|
};
|
|
158
161
|
//#endregion
|
|
159
|
-
export { JsonAuditLogger as default, severityFromStatus };
|
|
162
|
+
export { computeDurationMs, JsonAuditLogger as default, severityFromStatus };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { asServersConfig } from "../src/servers/kernelServers.js";
|
|
1
2
|
import { Service, extend } from "nodefony";
|
|
2
3
|
import { randomBytes } from "node:crypto";
|
|
3
4
|
import fsp from "node:fs/promises";
|
|
@@ -31,6 +32,15 @@ const defaultOptions = {
|
|
|
31
32
|
}
|
|
32
33
|
};
|
|
33
34
|
/**
|
|
35
|
+
* Valeur texte d'un champ de certificat node-forge (`getField` rend `any`),
|
|
36
|
+
* ou `null` si le champ est absent.
|
|
37
|
+
*/
|
|
38
|
+
function certFieldValue(field) {
|
|
39
|
+
if (field === null || typeof field !== "object") return null;
|
|
40
|
+
const value = field.value;
|
|
41
|
+
return typeof value === "string" ? value : String(value);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
34
44
|
* Service de fourniture du certificat TLS du serveur HTTPS/WSS.
|
|
35
45
|
*
|
|
36
46
|
* Trois stratégies : `explicit` (certificat fourni en config — le cas de
|
|
@@ -90,7 +100,7 @@ var Certificate = class Certificate extends Service {
|
|
|
90
100
|
* ne l'appelle jamais.
|
|
91
101
|
*/
|
|
92
102
|
async loadForge() {
|
|
93
|
-
|
|
103
|
+
this.forge ??= (await import("node-forge")).default;
|
|
94
104
|
return this.forge;
|
|
95
105
|
}
|
|
96
106
|
/** Accès au backend forge déjà chargé (lève si `loadForge` n'a pas été appelé). */
|
|
@@ -120,7 +130,7 @@ var Certificate = class Certificate extends Service {
|
|
|
120
130
|
* exemplaire. La promesse était écrite ; rien ne la tenait.
|
|
121
131
|
*/
|
|
122
132
|
get tlsWanted() {
|
|
123
|
-
return !!this.module.kernel?.options
|
|
133
|
+
return !!asServersConfig(this.module.kernel?.options.servers)?.https;
|
|
124
134
|
}
|
|
125
135
|
async init() {
|
|
126
136
|
if (!this.tlsWanted) return this;
|
|
@@ -138,8 +148,8 @@ var Certificate = class Certificate extends Service {
|
|
|
138
148
|
*/
|
|
139
149
|
static generateSerialHex() {
|
|
140
150
|
const bytes = randomBytes(16);
|
|
141
|
-
bytes
|
|
142
|
-
|
|
151
|
+
const head = bytes.readUInt8(0) & 127;
|
|
152
|
+
bytes[0] = head === 0 ? 1 : head;
|
|
143
153
|
return bytes.toString("hex");
|
|
144
154
|
}
|
|
145
155
|
setFiles() {
|
|
@@ -179,7 +189,7 @@ var Certificate = class Certificate extends Service {
|
|
|
179
189
|
path.dirname(this.certPath),
|
|
180
190
|
path.dirname(this.fullchainPath)
|
|
181
191
|
];
|
|
182
|
-
for
|
|
192
|
+
for (const directory of directories) try {
|
|
183
193
|
await fsp.access(directory);
|
|
184
194
|
} catch {
|
|
185
195
|
await fsp.mkdir(directory, { recursive: true });
|
|
@@ -237,7 +247,7 @@ var Certificate = class Certificate extends Service {
|
|
|
237
247
|
}
|
|
238
248
|
if (requested === "auto" && this.hasExplicitCert()) return "explicit";
|
|
239
249
|
if (requested === "mkcert" || requested === "auto") {
|
|
240
|
-
if (this.isDev() && this.certOptions.dev
|
|
250
|
+
if (this.isDev() && this.certOptions.dev.useMkcert !== false) {
|
|
241
251
|
const caRoot = await this.detectMkcert();
|
|
242
252
|
if (caRoot) {
|
|
243
253
|
this.mkcertCaRoot = caRoot;
|
|
@@ -345,8 +355,8 @@ var Certificate = class Certificate extends Service {
|
|
|
345
355
|
const ext = cert.getExtension("subjectAltName");
|
|
346
356
|
if (!ext || !this.sanCovers(ext.altNames ?? [])) return false;
|
|
347
357
|
if (strategy === "mkcert") {
|
|
348
|
-
const org = cert.issuer.getField("O");
|
|
349
|
-
return
|
|
358
|
+
const org = certFieldValue(cert.issuer.getField("O"));
|
|
359
|
+
return org !== null && /mkcert/i.test(org);
|
|
350
360
|
}
|
|
351
361
|
return cert.signatureOid !== pki.oids.sha1WithRSAEncryption;
|
|
352
362
|
} catch {
|
|
@@ -401,7 +411,7 @@ var Certificate = class Certificate extends Service {
|
|
|
401
411
|
}
|
|
402
412
|
async writeCertificates(force = false) {
|
|
403
413
|
await this.ensureDirectoriesExist();
|
|
404
|
-
for
|
|
414
|
+
for (const file of this.files) try {
|
|
405
415
|
if (file.variable) {
|
|
406
416
|
const mode = file.path === this.privateKeyPath ? this.certOptions.privateKeyMode ?? 384 : 420;
|
|
407
417
|
if (force) await fsp.rm(file.path, { force: true });
|
|
@@ -542,8 +552,8 @@ var Certificate = class Certificate extends Service {
|
|
|
542
552
|
info.validFrom = cert.validity.notBefore.toISOString();
|
|
543
553
|
info.validTo = cert.validity.notAfter.toISOString();
|
|
544
554
|
info.signatureAlgorithm = this.oidName(cert.signatureOid, pki);
|
|
545
|
-
const cn = cert.subject.getField("CN");
|
|
546
|
-
if (cn) info.commonName =
|
|
555
|
+
const cn = certFieldValue(cert.subject.getField("CN"));
|
|
556
|
+
if (cn !== null) info.commonName = cn;
|
|
547
557
|
const ext = cert.getExtension("subjectAltName");
|
|
548
558
|
if (ext?.altNames) info.san = ext.altNames.map((a) => a.type === 7 ? a.ip ?? "" : a.value ?? "");
|
|
549
559
|
} catch {}
|
|
@@ -560,9 +570,11 @@ var Certificate = class Certificate extends Service {
|
|
|
560
570
|
case "sha512": return md.sha512.create();
|
|
561
571
|
case "sha384": return md.sha384.create();
|
|
562
572
|
case "sha256": return md.sha256.create();
|
|
563
|
-
default:
|
|
564
|
-
|
|
573
|
+
default: {
|
|
574
|
+
const refused = hash;
|
|
575
|
+
this.log(`Hachage '${refused}' refusé (SHA-1 interdit) → SHA-256.`, "WARNING");
|
|
565
576
|
return md.sha256.create();
|
|
577
|
+
}
|
|
566
578
|
}
|
|
567
579
|
}
|
|
568
580
|
};
|
|
@@ -111,7 +111,7 @@ function toValidationFields(error) {
|
|
|
111
111
|
function toValidationError(error, fields) {
|
|
112
112
|
const summary = fields.map((f) => f.field ? `${f.field}: ${f.message}` : f.message).join(" · ");
|
|
113
113
|
const validation = new nodefonyError(`Validation failed — ${summary}`, VALIDATION_STATUS);
|
|
114
|
-
validation.stack = error.stack;
|
|
114
|
+
if (error.stack !== void 0) validation.stack = error.stack;
|
|
115
115
|
validation.fields = fields;
|
|
116
116
|
return validation;
|
|
117
117
|
}
|
|
@@ -206,8 +206,11 @@ function malformedIdentifierOf(error) {
|
|
|
206
206
|
const candidate = current;
|
|
207
207
|
if (candidate.name === "CastError" && (candidate.kind === "ObjectId" || candidate.kind === "UUID") && (typeof candidate.value === "string" || typeof candidate.value === "number")) return String(candidate.value);
|
|
208
208
|
if (String(candidate.code) === "22P02") {
|
|
209
|
-
const match = PG_INVALID_INPUT.exec(candidate.message
|
|
210
|
-
if (match
|
|
209
|
+
const match = PG_INVALID_INPUT.exec(candidate.message);
|
|
210
|
+
if (match) {
|
|
211
|
+
const [, type = "", value = null] = match;
|
|
212
|
+
if (IDENTIFIER_COLUMN_TYPES.has(type)) return value;
|
|
213
|
+
}
|
|
211
214
|
}
|
|
212
215
|
current = candidate.cause;
|
|
213
216
|
}
|
|
@@ -307,7 +310,7 @@ function schemaMismatchOf(error) {
|
|
|
307
310
|
for (let depth = 0; depth < 5 && current instanceof Error; depth += 1) {
|
|
308
311
|
const candidate = current;
|
|
309
312
|
if (candidate.code !== void 0 && SCHEMA_MISMATCH_CODES.has(String(candidate.code)) || candidate.errno !== void 0 && SCHEMA_MISMATCH_CODES.has(String(candidate.errno))) return "certain";
|
|
310
|
-
if (String(candidate.code) === "SQLITE_ERROR" && SQLITE_SCHEMA_PREFIXES.some((p) => candidate.message
|
|
313
|
+
if (String(candidate.code) === "SQLITE_ERROR" && SQLITE_SCHEMA_PREFIXES.some((p) => candidate.message.startsWith(p))) return "probable";
|
|
311
314
|
current = candidate.cause;
|
|
312
315
|
}
|
|
313
316
|
return null;
|
|
@@ -373,7 +376,7 @@ var DefaultErrorRenderer = class {
|
|
|
373
376
|
const httpError = this.toHttpError(error, context);
|
|
374
377
|
const rawCode = httpError.code;
|
|
375
378
|
let code = typeof rawCode === "number" && Number.isInteger(rawCode) ? rawCode : 500;
|
|
376
|
-
if (context
|
|
379
|
+
if (context.rejected === false) code = toWsCloseCode(code);
|
|
377
380
|
else if (code > 599) code = 500;
|
|
378
381
|
const internal = code === 1011 || code >= 500 && code <= 599;
|
|
379
382
|
const reason = isProduction() && internal ? OPAQUE_SERVER_ERROR : httpError.message;
|
|
@@ -392,20 +395,20 @@ var DefaultErrorRenderer = class {
|
|
|
392
395
|
}
|
|
393
396
|
if (isUniqueViolation(error)) {
|
|
394
397
|
const conflict = new nodefonyError(UNIQUE_VIOLATION_MESSAGE, CONFLICT_STATUS);
|
|
395
|
-
conflict.stack = error.stack;
|
|
398
|
+
if (error.stack !== void 0) conflict.stack = error.stack;
|
|
396
399
|
return new HttpError(conflict, CONFLICT_STATUS, context);
|
|
397
400
|
}
|
|
398
401
|
const referencedBy = referencedByOf(error);
|
|
399
402
|
if (referencedBy !== null) {
|
|
400
403
|
const conflict = new nodefonyError(`Conflict — this resource is still referenced by ${referencedBy}`, CONFLICT_STATUS);
|
|
401
|
-
conflict.stack = error.stack;
|
|
404
|
+
if (error.stack !== void 0) conflict.stack = error.stack;
|
|
402
405
|
return new HttpError(conflict, CONFLICT_STATUS, context);
|
|
403
406
|
}
|
|
404
407
|
const malformed = malformedIdentifierOf(error);
|
|
405
408
|
if (malformed !== null) {
|
|
406
409
|
const status = malformedIdentifierStatus(malformed, context?.url);
|
|
407
410
|
const refused = new nodefonyError(status === 404 ? "Not Found — this identifier cannot designate any resource" : "Bad Request — malformed identifier", status);
|
|
408
|
-
refused.stack = error.stack;
|
|
411
|
+
if (error.stack !== void 0) refused.stack = error.stack;
|
|
409
412
|
return new HttpError(refused, status, context);
|
|
410
413
|
}
|
|
411
414
|
const code = error.code;
|