@blamejs/core 0.15.11 → 0.15.13
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/CHANGELOG.md +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +7 -23
- package/lib/acme.js +13 -16
- package/lib/agent-event-bus.js +5 -4
- package/lib/agent-idempotency.js +2 -5
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-capability.js +1 -6
- package/lib/ai-dp.js +1 -5
- package/lib/ai-input.js +2 -2
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-read.js +5 -17
- package/lib/archive-tar-read.js +5 -16
- package/lib/archive.js +2 -10
- package/lib/arg-parser.js +7 -6
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +108 -31
- package/lib/audit-chain.js +133 -53
- package/lib/audit-daily-review.js +24 -14
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +257 -0
- package/lib/audit.js +84 -0
- package/lib/auth/access-lock.js +5 -27
- package/lib/auth/dpop.js +23 -35
- package/lib/auth/fido-mds3.js +9 -15
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +13 -15
- package/lib/auth/lockout.js +6 -25
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +3 -3
- package/lib/auth/password.js +7 -1
- package/lib/auth/saml.js +37 -27
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +1 -1
- package/lib/auth/status-list.js +7 -7
- package/lib/auth/step-up.js +6 -12
- package/lib/auth-bot-challenge.js +6 -37
- package/lib/backup/bundle.js +11 -18
- package/lib/backup/index.js +14 -47
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/cache.js +7 -18
- package/lib/cbor.js +2 -1
- package/lib/cdn-cache-control.js +8 -9
- package/lib/cert.js +3 -10
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +5 -1
- package/lib/client-hints.js +10 -10
- package/lib/cloud-events.js +40 -31
- package/lib/cluster-storage.js +2 -38
- package/lib/cluster.js +4 -2
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +67 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +1 -11
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +2 -14
- package/lib/compliance.js +2 -9
- package/lib/config-drift.js +2 -12
- package/lib/content-digest.js +10 -8
- package/lib/cookies.js +5 -11
- package/lib/cose.js +19 -11
- package/lib/cra-report.js +1 -11
- package/lib/crypto-field.js +5 -10
- package/lib/crypto.js +120 -3
- package/lib/csp.js +235 -3
- package/lib/daemon.js +42 -41
- package/lib/data-act.js +19 -9
- package/lib/db-query.js +6 -40
- package/lib/db.js +34 -12
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dsr.js +8 -12
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +14 -26
- package/lib/fda-21cfr11.js +12 -8
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +869 -38
- package/lib/gdpr-ropa.js +4 -13
- package/lib/graphql-federation.js +1 -1
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +1 -0
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +16 -13
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +1 -1
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +18 -62
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +65 -117
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +8 -9
- package/lib/guard-json.js +87 -103
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +2 -1
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +5 -4
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +12 -13
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +8 -11
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +95 -140
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/http-client-cache.js +8 -13
- package/lib/http-client-cookie-jar.js +2 -4
- package/lib/http-client.js +8 -21
- package/lib/http-message-signature.js +26 -8
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +3 -21
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +6 -15
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +28 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-syslog.js +6 -0
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +24 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +3 -2
- package/lib/mail-auth.js +40 -66
- package/lib/mail-bimi.js +19 -39
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +6 -9
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +15 -26
- package/lib/mail-helo.js +2 -3
- package/lib/mail-journal.js +2 -1
- package/lib/mail-mdn.js +4 -3
- package/lib/mail-rbl.js +5 -8
- package/lib/mail-scan.js +2 -2
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +35 -51
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +89 -0
- package/lib/mail-spam-score.js +7 -8
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +11 -11
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +54 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +1 -1
- package/lib/mdoc.js +11 -12
- package/lib/metrics.js +32 -30
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +11 -22
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +79 -54
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +2 -10
- package/lib/middleware/csrf-protect.js +13 -2
- package/lib/middleware/daily-byte-quota.js +6 -26
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +7 -0
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/rate-limit.js +20 -28
- package/lib/middleware/scim-server.js +3 -2
- package/lib/middleware/security-headers.js +9 -1
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/tus-upload.js +12 -27
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +116 -36
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +97 -6
- package/lib/network-dns.js +22 -26
- package/lib/network-dnssec.js +16 -16
- package/lib/network-heartbeat.js +6 -5
- package/lib/network-proxy.js +3 -7
- package/lib/network-smtp-policy.js +29 -41
- package/lib/network-tls.js +74 -55
- package/lib/network.js +2 -6
- package/lib/nis2-report.js +1 -11
- package/lib/nonce-store.js +81 -3
- package/lib/notify.js +7 -12
- package/lib/numeric-bounds.js +22 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +5 -45
- package/lib/object-store/gcs.js +8 -71
- package/lib/object-store/http-put.js +1 -5
- package/lib/object-store/http-request.js +128 -0
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +9 -77
- package/lib/observability-otlp-exporter.js +9 -25
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +43 -37
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +2 -2
- package/lib/parsers/safe-yaml.js +7 -6
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/redact.js +2 -1
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +133 -6
- package/lib/restore.js +5 -22
- package/lib/retention.js +3 -5
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +12 -26
- package/lib/safe-json.js +7 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +12 -26
- package/lib/sandbox-worker.js +9 -2
- package/lib/sandbox.js +3 -2
- package/lib/scheduler.js +5 -12
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +9 -21
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +194 -6
- package/lib/sql.js +225 -78
- package/lib/sse.js +6 -10
- package/lib/static.js +136 -98
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +313 -17
- package/lib/tenant-quota.js +2 -20
- package/lib/tsa.js +11 -15
- package/lib/validate-opts.js +154 -8
- package/lib/vault/seal-pem-file.js +30 -48
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +2 -7
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +43 -18
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +7 -3
- package/lib/worm.js +2 -3
- package/lib/ws-client.js +8 -10
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
var lazyRequire = require("../lazy-require");
|
|
29
29
|
var validateOpts = require("../validate-opts");
|
|
30
|
+
var denyResponse = require("./deny-response");
|
|
30
31
|
var { defineClass } = require("../framework-error");
|
|
31
32
|
|
|
32
33
|
var SecurityTxtError = defineClass("SecurityTxtError", { alwaysPermanent: true });
|
|
@@ -143,12 +144,7 @@ function create(opts) {
|
|
|
143
144
|
(alsoAtRoot && path === "/security.txt");
|
|
144
145
|
if (!matches) return next();
|
|
145
146
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
146
|
-
|
|
147
|
-
"Allow": "GET, HEAD",
|
|
148
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
149
|
-
"Content-Length": 18, // len of "Method Not Allowed"
|
|
150
|
-
});
|
|
151
|
-
res.end("Method Not Allowed");
|
|
147
|
+
denyResponse.methodNotAllowed(res, "GET, HEAD");
|
|
152
148
|
return;
|
|
153
149
|
}
|
|
154
150
|
res.writeHead(200, { // HTTP 200 status
|
|
@@ -150,10 +150,10 @@ function _parseChecksumHeader(headerValue, allowedSet) {
|
|
|
150
150
|
// alphabet); refuse those on the RAW value BEFORE the slice/trim
|
|
151
151
|
// normalisation (same v0.8.90 trim-before-validate bug class).
|
|
152
152
|
if (structuredFields.containsControlBytes(headerValue)) return { error: "malformed" };
|
|
153
|
-
var
|
|
154
|
-
if (
|
|
155
|
-
var algo =
|
|
156
|
-
var digestB64 =
|
|
153
|
+
var kvp = structuredFields.parseKeyValuePiece(headerValue, " ");
|
|
154
|
+
if (kvp.value === null) return { error: "malformed" };
|
|
155
|
+
var algo = kvp.key;
|
|
156
|
+
var digestB64 = kvp.value.trim();
|
|
157
157
|
if (algo.length === 0 || digestB64.length === 0) return { error: "malformed" };
|
|
158
158
|
if (!allowedSet[algo]) return { error: "algo-unsupported" };
|
|
159
159
|
if (!/^[A-Za-z0-9+/=]+$/.test(digestB64)) return { error: "malformed" };
|
|
@@ -311,19 +311,11 @@ function _emitTusBaseHeaders(res, extra) {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
function _readChunk(req, maxChunkSize) {
|
|
314
|
-
return
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
sizeMessage: "PATCH body exceeded maxChunkSize",
|
|
320
|
-
});
|
|
321
|
-
req.on("data", function (c) {
|
|
322
|
-
try { collector.push(c); }
|
|
323
|
-
catch (e) { req.removeAllListeners("data"); reject(e); }
|
|
324
|
-
});
|
|
325
|
-
req.on("end", function () { resolve(collector.result()); });
|
|
326
|
-
req.on("error", function (e) { reject(e); });
|
|
314
|
+
return safeBuffer.collectStream(req, {
|
|
315
|
+
maxBytes: maxChunkSize,
|
|
316
|
+
errorClass: TusError,
|
|
317
|
+
sizeCode: "tus/chunk-too-large",
|
|
318
|
+
sizeMessage: "PATCH body exceeded maxChunkSize",
|
|
327
319
|
});
|
|
328
320
|
}
|
|
329
321
|
|
|
@@ -388,11 +380,8 @@ function create(opts) {
|
|
|
388
380
|
}
|
|
389
381
|
|
|
390
382
|
var store = opts.store;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
throw new TusError("tus/bad-store",
|
|
394
|
-
"middleware.tusUpload: store must implement { create, head, append, terminate }");
|
|
395
|
-
}
|
|
383
|
+
validateOpts.requireMethods(store, ["create", "head", "append", "terminate"],
|
|
384
|
+
"middleware.tusUpload: store", TusError, "tus/bad-store");
|
|
396
385
|
|
|
397
386
|
var maxSize = opts.maxSize;
|
|
398
387
|
if (maxSize !== undefined && (typeof maxSize !== "number" || !isFinite(maxSize) || maxSize <= 0)) {
|
|
@@ -451,11 +440,7 @@ function create(opts) {
|
|
|
451
440
|
return new Date(rec.expireAt).toUTCString();
|
|
452
441
|
}
|
|
453
442
|
|
|
454
|
-
|
|
455
|
-
if (!auditOn) return;
|
|
456
|
-
try { observability().safeEvent("middleware.tusUpload." + verb, 1, {}); }
|
|
457
|
-
catch (_e) { /* drop-silent — observability sink */ }
|
|
458
|
-
}
|
|
443
|
+
var _emitMetric = observability().namespaced("middleware.tusUpload", auditOn);
|
|
459
444
|
|
|
460
445
|
async function _handleOptions(req, res) {
|
|
461
446
|
var headers = _emitTusBaseHeaders(res, {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
var lazyRequire = require("../lazy-require");
|
|
30
30
|
var safeJson = require("../safe-json");
|
|
31
31
|
var validateOpts = require("../validate-opts");
|
|
32
|
+
var denyResponse = require("./deny-response");
|
|
32
33
|
var { defineClass } = require("../framework-error");
|
|
33
34
|
|
|
34
35
|
var WebAppManifestError = defineClass("WebAppManifestError", { alwaysPermanent: true });
|
|
@@ -135,13 +136,7 @@ function create(opts) {
|
|
|
135
136
|
(alsoAtJsonPath && path === "/manifest.json");
|
|
136
137
|
if (!matches) return next();
|
|
137
138
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
138
|
-
|
|
139
|
-
res.writeHead(405, { // HTTP 405 status
|
|
140
|
-
"Allow": "GET, HEAD",
|
|
141
|
-
"Content-Type": "text/plain; charset=utf-8",
|
|
142
|
-
"Content-Length": Buffer.byteLength(bodyMsg),
|
|
143
|
-
});
|
|
144
|
-
res.end(bodyMsg);
|
|
139
|
+
denyResponse.methodNotAllowed(res, "GET, HEAD");
|
|
145
140
|
return;
|
|
146
141
|
}
|
|
147
142
|
res.writeHead(200, { // HTTP 200 status
|
package/lib/migrations.js
CHANGED
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
var nodePath = require("node:path");
|
|
42
|
+
var moduleLoader = require("./module-loader");
|
|
42
43
|
var atomicFile = require("./atomic-file");
|
|
43
44
|
var dbSchema = require("./db-schema");
|
|
44
45
|
var frameworkSchema = require("./framework-schema");
|
|
@@ -269,21 +270,11 @@ function _resolveDb(opts) {
|
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
function _loadMigration(file, dir) {
|
|
272
|
-
var
|
|
273
|
-
|
|
274
|
-
// changes a migration file between calls picks up the new content.
|
|
275
|
-
// Production deployments would always restart the process, but this
|
|
276
|
-
// keeps test fixtures sane.
|
|
277
|
-
try { delete require.cache[require.resolve(fullPath)]; } catch (_e) { /* not yet cached */ }
|
|
278
|
-
var mod;
|
|
279
|
-
// Operator-supplied migration — dynamic by design, can't be bundle-
|
|
280
|
-
// traced. Host-CLI scope; deploying via SEA / pkg drops this surface.
|
|
281
|
-
try { mod = require(fullPath); } // allow:dynamic-require — operator-supplied migration
|
|
282
|
-
catch (e) {
|
|
283
|
-
throw new MigrationError("migrations/load-failed",
|
|
273
|
+
var mod = moduleLoader.requireFresh(nodePath.join(dir, file), function (e) {
|
|
274
|
+
return new MigrationError("migrations/load-failed",
|
|
284
275
|
"migration '" + file + "' failed to load: " + ((e && e.message) || String(e)),
|
|
285
276
|
true);
|
|
286
|
-
}
|
|
277
|
+
});
|
|
287
278
|
if (!mod || typeof mod.up !== "function") {
|
|
288
279
|
throw new MigrationError("migrations/missing-up",
|
|
289
280
|
"migration '" + file + "' must export an `up(db)` function", true);
|
package/lib/mime-parse.js
CHANGED
|
@@ -37,32 +37,48 @@
|
|
|
37
37
|
* mdn: 1 MiB) so this module's hot-path doesn't add its own cap.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
-
function
|
|
41
|
-
// RFC 5322 §2.2
|
|
42
|
-
//
|
|
43
|
-
// the
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
function classifyHeaderBlock(text) {
|
|
41
|
+
// RFC 5322 §2.2 — every line of a header section is either a header field
|
|
42
|
+
// (`name: value`), a folding continuation (leading WSP), or the empty line
|
|
43
|
+
// that ends the section. Anything else is MALFORMED — and a colon-less line
|
|
44
|
+
// sitting in the header section is the header-injection / SMTP-smuggling
|
|
45
|
+
// signal (attacker content on a bare line followed by an injected
|
|
46
|
+
// Bcc/To/From header). `fields` is the parsed headers (boundary-aware,
|
|
47
|
+
// continuation-unfolded); `malformed` is the injection/structure evidence the
|
|
48
|
+
// silent-skip parsers (email, mime, dsn, arc) all dropped on the floor.
|
|
49
|
+
var lines = String(text == null ? "" : text).split(/\r?\n/);
|
|
50
|
+
var unfolded = []; // [{ line, lineIndex }]
|
|
46
51
|
for (var i = 0; i < lines.length; i += 1) {
|
|
47
52
|
var line = lines[i];
|
|
48
|
-
if (line.length === 0) break;
|
|
53
|
+
if (line.length === 0) break; // header/body boundary
|
|
49
54
|
if ((line.charAt(0) === " " || line.charAt(0) === "\t") && unfolded.length > 0) {
|
|
50
|
-
unfolded[unfolded.length - 1] += " " + line.replace(/^\s+/, "");
|
|
55
|
+
unfolded[unfolded.length - 1].line += " " + line.replace(/^\s+/, "");
|
|
51
56
|
} else {
|
|
52
|
-
unfolded.push(line);
|
|
57
|
+
unfolded.push({ line: line, lineIndex: i });
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
|
-
var
|
|
60
|
+
var fields = [];
|
|
61
|
+
var malformed = [];
|
|
56
62
|
for (var j = 0; j < unfolded.length; j += 1) {
|
|
57
|
-
var
|
|
58
|
-
var colonAt =
|
|
59
|
-
if (colonAt === -1)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
var entry = unfolded[j];
|
|
64
|
+
var colonAt = entry.line.indexOf(":");
|
|
65
|
+
if (colonAt === -1) {
|
|
66
|
+
malformed.push({ lineIndex: entry.lineIndex, line: entry.line, reason: "no-colon" });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
fields.push({
|
|
70
|
+
name: entry.line.slice(0, colonAt).trim(),
|
|
71
|
+
value: entry.line.slice(colonAt + 1).trim(),
|
|
63
72
|
});
|
|
64
73
|
}
|
|
65
|
-
return
|
|
74
|
+
return { fields: fields, malformed: malformed };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseHeaderBlock(text) {
|
|
78
|
+
// The fields view of the shared classifier — kept for every caller that only
|
|
79
|
+
// wants the parsed headers (the malformed lines are reached via
|
|
80
|
+
// classifyHeaderBlock for structure/injection validation).
|
|
81
|
+
return classifyHeaderBlock(text).fields;
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
function splitHeadersAndBody(text) {
|
|
@@ -189,6 +205,7 @@ function addressType(addr) {
|
|
|
189
205
|
|
|
190
206
|
module.exports = {
|
|
191
207
|
parseHeaderBlock: parseHeaderBlock,
|
|
208
|
+
classifyHeaderBlock: classifyHeaderBlock,
|
|
192
209
|
splitHeadersAndBody: splitHeadersAndBody,
|
|
193
210
|
findHeader: findHeader,
|
|
194
211
|
parseContentType: parseContentType,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* lib/module-loader.js — the single audited entry point for loading an
|
|
4
|
+
* operator-supplied module file (a migration or a seed) by path.
|
|
5
|
+
*
|
|
6
|
+
* Operator modules execute arbitrary code on `require`, so the dynamic
|
|
7
|
+
* `require()` lives here behind ONE `allow:dynamic-require` marker rather
|
|
8
|
+
* than scattered across every host-CLI loader (`b.migrations`,
|
|
9
|
+
* `b.seeders`, `b.externalDb.migrate`). The require cache is busted first
|
|
10
|
+
* so a dev / test rewriting a fixture between calls picks up the new
|
|
11
|
+
* content (production deployments restart the process). The dynamic
|
|
12
|
+
* require does not survive SEA / esbuild bundling — operator migrations
|
|
13
|
+
* and seeds are host-CLI scope, not framework-internal scope.
|
|
14
|
+
*
|
|
15
|
+
* The caller owns path construction (it knows its own directory layout)
|
|
16
|
+
* and the typed, domain-specific error it throws on a load failure
|
|
17
|
+
* (parse error, throwing top-level code), so resolution stays identical
|
|
18
|
+
* to the per-loader form it replaces.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* requireFresh(absPath, onLoadError) — bust the require cache for
|
|
23
|
+
* `absPath`, then require it fresh. `absPath` MUST be absolute (a bare
|
|
24
|
+
* relative path would resolve against node_modules, which is never an
|
|
25
|
+
* operator file). On a load failure, `onLoadError(err)` builds the
|
|
26
|
+
* caller's typed error, which is thrown.
|
|
27
|
+
*
|
|
28
|
+
* var mod = moduleLoader.requireFresh(path.join(dir, file), function (e) {
|
|
29
|
+
* return new MigrationError("migrations/load-failed",
|
|
30
|
+
* "migration '" + file + "' failed to load: " + ((e && e.message) || String(e)), true);
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
function requireFresh(absPath, onLoadError) {
|
|
34
|
+
try { delete require.cache[require.resolve(absPath)]; } catch (_e) { /* not yet cached */ }
|
|
35
|
+
try {
|
|
36
|
+
return require(absPath); // allow:dynamic-require — operator-supplied module (migration / seed)
|
|
37
|
+
} catch (e) {
|
|
38
|
+
throw onLoadError(e);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
requireFresh: requireFresh,
|
|
44
|
+
};
|
package/lib/money.js
CHANGED
|
@@ -277,6 +277,32 @@ Money.prototype.multiply = function (factor, opts) {
|
|
|
277
277
|
return new Money(quotient, this.currency);
|
|
278
278
|
};
|
|
279
279
|
|
|
280
|
+
/**
|
|
281
|
+
* @primitive b.money.Money.roundToIncrement
|
|
282
|
+
* @signature b.money.Money.roundToIncrement(step, opts?)
|
|
283
|
+
* @since 0.15.13
|
|
284
|
+
* @status stable
|
|
285
|
+
* @related b.money.roundMinor, b.money.Money
|
|
286
|
+
*
|
|
287
|
+
* Snap this amount to the nearest multiple of `step` minor units, returning a
|
|
288
|
+
* new `Money` in the same currency — the cash-rounding step a coarser
|
|
289
|
+
* denomination needs (CHF to the nearest 0.05, SEK to the nearest 0.10, a
|
|
290
|
+
* 100-unit price step) without leaving the type. Integer-only; immutable;
|
|
291
|
+
* negative amounts (refund previews) round on the correct side of the tie. See
|
|
292
|
+
* `b.money.roundMinor` for the raw-integer form and the mode semantics.
|
|
293
|
+
*
|
|
294
|
+
* @opts
|
|
295
|
+
* mode: "half-even" | "half-up" | "half-down" | "ceiling" | "floor", // default: half-even
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* b.money.of("12.32", "CHF").roundToIncrement(5, { mode: "half-up" }); // → CHF 12.30
|
|
299
|
+
* b.money.of("19.97", "SEK").roundToIncrement(10); // → SEK 20.00
|
|
300
|
+
*/
|
|
301
|
+
Money.prototype.roundToIncrement = function (step, opts) {
|
|
302
|
+
opts = opts || {};
|
|
303
|
+
return new Money(roundMinor(this._minor, step, opts.mode || "half-even"), this.currency);
|
|
304
|
+
};
|
|
305
|
+
|
|
280
306
|
// _rationalFromDecimalString -- `"1.085"` -> { num: 1085n, den: 1000n }.
|
|
281
307
|
// Strict shape; same refusals as _parseDecimalString.
|
|
282
308
|
function _rationalFromDecimalString(s) {
|
|
@@ -333,6 +359,84 @@ function _divRound(n, d, rounding) {
|
|
|
333
359
|
return r < 0n ? q - 1n : q + 1n;
|
|
334
360
|
}
|
|
335
361
|
|
|
362
|
+
// Cash-rounding modes for roundMinor / Money.roundToIncrement. half-even
|
|
363
|
+
// (banker's) is the default and matches the rest of the module; half-up rounds
|
|
364
|
+
// a tie away from zero, half-down toward zero; ceiling/floor are directional
|
|
365
|
+
// (toward +inf / -inf) regardless of distance.
|
|
366
|
+
var INCREMENT_MODES = {
|
|
367
|
+
"half-even": true, "half-up": true, "half-down": true, "ceiling": true, "floor": true,
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
function _toIncrementBigInt(step) {
|
|
371
|
+
if (typeof step === "bigint") {
|
|
372
|
+
if (step <= 0n) throw new MoneyError("money/bad-increment",
|
|
373
|
+
"step must be a positive integer of minor units");
|
|
374
|
+
return step;
|
|
375
|
+
}
|
|
376
|
+
if (typeof step === "number" && Number.isInteger(step) && step > 0) return BigInt(step);
|
|
377
|
+
throw new MoneyError("money/bad-increment",
|
|
378
|
+
"step must be a positive integer (BigInt or safe integer Number) of minor units; got " +
|
|
379
|
+
(typeof step === "number" ? String(step) : typeof step));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @primitive b.money.roundMinor
|
|
384
|
+
* @signature b.money.roundMinor(minor, step, mode?)
|
|
385
|
+
* @since 0.15.13
|
|
386
|
+
* @status stable
|
|
387
|
+
* @related b.money.Money, b.money.fromMinorUnits
|
|
388
|
+
*
|
|
389
|
+
* Snap a raw minor-unit integer to the nearest multiple of `step` minor units —
|
|
390
|
+
* the cash-rounding step a coarser cash denomination needs even though the
|
|
391
|
+
* currency's ISO 4217 minor unit is finer (a CHF total to the nearest 5 rappen
|
|
392
|
+
* after the 1/2-rappen coins were retired, a SEK total to the nearest 10 öre, a
|
|
393
|
+
* JPY total to a 100-unit psychological-pricing step). Pure BigInt math — no
|
|
394
|
+
* `Number` in the value path — and the remainder sign is handled so a negative
|
|
395
|
+
* amount (a refund preview) rounds on the correct side of the tie.
|
|
396
|
+
*
|
|
397
|
+
* `minor` accepts a BigInt or a safe integer `Number`; the return is a BigInt.
|
|
398
|
+
*
|
|
399
|
+
* @opts
|
|
400
|
+
* mode: "half-even" | "half-up" | "half-down" | "ceiling" | "floor", // default: half-even
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* b.money.roundMinor(1232n, 5n); // CHF 12.32 → nearest 0.05 → 1230n (12.30)
|
|
404
|
+
* b.money.roundMinor(25n, 10n, "half-even"); // tie → even multiple → 20n
|
|
405
|
+
* b.money.roundMinor(25n, 10n, "half-up"); // tie away from zero → 30n
|
|
406
|
+
* b.money.roundMinor(-25n, 10n, "half-up"); // refund tie away from zero → -30n
|
|
407
|
+
*/
|
|
408
|
+
function roundMinor(minor, step, mode) {
|
|
409
|
+
mode = mode || "half-even";
|
|
410
|
+
if (typeof minor === "number" && Number.isInteger(minor)) minor = BigInt(minor);
|
|
411
|
+
if (typeof minor !== "bigint") {
|
|
412
|
+
throw new MoneyError("money/bad-minor-units",
|
|
413
|
+
"minor must be an integer (BigInt or safe integer Number); got " + (typeof minor));
|
|
414
|
+
}
|
|
415
|
+
if (!INCREMENT_MODES[mode]) {
|
|
416
|
+
throw new MoneyError("money/bad-rounding-mode",
|
|
417
|
+
"mode must be one of half-even | half-up | half-down | ceiling | floor; got " + String(mode));
|
|
418
|
+
}
|
|
419
|
+
var stepBig = _toIncrementBigInt(step);
|
|
420
|
+
var q = minor / stepBig;
|
|
421
|
+
var r = minor - q * stepBig;
|
|
422
|
+
if (r === 0n) return minor;
|
|
423
|
+
// lower = the multiple toward -inf, upper = toward +inf. BigInt division
|
|
424
|
+
// truncates toward zero, so the bracket flips by sign of `minor`.
|
|
425
|
+
var lower, upper;
|
|
426
|
+
if (minor >= 0n) { lower = q * stepBig; upper = lower + stepBig; }
|
|
427
|
+
else { upper = q * stepBig; lower = upper - stepBig; }
|
|
428
|
+
if (mode === "floor") return lower;
|
|
429
|
+
if (mode === "ceiling") return upper;
|
|
430
|
+
var distLow = minor - lower; // >= 0
|
|
431
|
+
var distHigh = upper - minor; // >= 0
|
|
432
|
+
if (distLow < distHigh) return lower;
|
|
433
|
+
if (distHigh < distLow) return upper;
|
|
434
|
+
// Exactly on the tie.
|
|
435
|
+
if (mode === "half-up") return (minor >= 0n) ? upper : lower; // away from zero
|
|
436
|
+
if (mode === "half-down") return (minor >= 0n) ? lower : upper; // toward zero
|
|
437
|
+
return ((lower / stepBig) % 2n === 0n) ? lower : upper; // half-even
|
|
438
|
+
}
|
|
439
|
+
|
|
336
440
|
// allocate -- split `this` into `weights.length` parts proportional to
|
|
337
441
|
// the weights, distributing every minor unit. Largest-remainder
|
|
338
442
|
// method: floor each share, then hand out the leftover units to the
|
|
@@ -693,6 +797,7 @@ module.exports = {
|
|
|
693
797
|
parse: parse,
|
|
694
798
|
zero: zero,
|
|
695
799
|
convert: convert,
|
|
800
|
+
roundMinor: roundMinor,
|
|
696
801
|
CURRENCIES: CURRENCIES,
|
|
697
802
|
Money: Money,
|
|
698
803
|
MoneyError: MoneyError,
|
package/lib/mtls-ca.js
CHANGED
|
@@ -58,6 +58,9 @@ var nodeCrypto = require("node:crypto");
|
|
|
58
58
|
var atomicFile = require("./atomic-file");
|
|
59
59
|
var C = require("./constants");
|
|
60
60
|
var lazyRequire = require("./lazy-require");
|
|
61
|
+
// Lazy — the SHA3-512 fingerprint surfaced from issuance must match the one
|
|
62
|
+
// the require-mtls gate pins (b.crypto.sha3Hash(certPem)).
|
|
63
|
+
var bCrypto = lazyRequire(function () { return require("./crypto"); });
|
|
61
64
|
var { boot } = require("./log");
|
|
62
65
|
var safeBuffer = require("./safe-buffer");
|
|
63
66
|
var safeJson = require("./safe-json");
|
|
@@ -193,7 +196,7 @@ function create(opts) {
|
|
|
193
196
|
opts = opts || {};
|
|
194
197
|
validateOpts(opts, [
|
|
195
198
|
"dataDir", "paths", "vault",
|
|
196
|
-
"caKeySealedMode", "generation", "engine",
|
|
199
|
+
"caKeySealedMode", "generation", "engine", "revocationStore",
|
|
197
200
|
], "b.mtlsCa");
|
|
198
201
|
validateOpts.requireNonEmptyString(opts.dataDir, "mtlsCa.create: opts.dataDir", MtlsCaError, "mtls-ca/no-datadir");
|
|
199
202
|
// Auto-create the dataDir with restrictive perms (CA keys live here).
|
|
@@ -370,6 +373,29 @@ function create(opts) {
|
|
|
370
373
|
return fresh;
|
|
371
374
|
}
|
|
372
375
|
|
|
376
|
+
// Recover the issued certificate's identity from its PEM so issuance and
|
|
377
|
+
// any later revocation/indexing share identifiers without a round-trip back
|
|
378
|
+
// through an X.509 parser. serialNumber is normalized to the same hex form
|
|
379
|
+
// revoke() stores; fingerprint is the SHA3-512 the require-mtls gate pins.
|
|
380
|
+
function _certIdentity(certPem) {
|
|
381
|
+
// serialNumber comes from an X.509 parse, which is best-effort: a custom
|
|
382
|
+
// engine (or a test double) may return a cert in a shape this Node build
|
|
383
|
+
// cannot parse as X.509. The fingerprint is a hash of the returned bytes,
|
|
384
|
+
// so it is always available and is what the require-mtls gate pins —
|
|
385
|
+
// revoke()/isRevoked() by fingerprint keep working even when the serial
|
|
386
|
+
// can't be recovered. Never let optional identity enrichment crash issuance.
|
|
387
|
+
var serialNumber = null;
|
|
388
|
+
try {
|
|
389
|
+
serialNumber = _normalizeSerial(new nodeCrypto.X509Certificate(certPem).serialNumber);
|
|
390
|
+
} catch (_e) {
|
|
391
|
+
serialNumber = null;
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
serialNumber: serialNumber,
|
|
395
|
+
fingerprint: bCrypto().sha3Hash(certPem),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
373
399
|
async function generateClientCert(opts2) {
|
|
374
400
|
opts2 = opts2 || {};
|
|
375
401
|
var ca = await initCA();
|
|
@@ -379,7 +405,10 @@ function create(opts) {
|
|
|
379
405
|
throw new MtlsCaError("mtls-ca/bad-engine-output",
|
|
380
406
|
"engine.signClientCert must return { cert, key, ca?, issuedAt?, expiresAt? }");
|
|
381
407
|
}
|
|
382
|
-
|
|
408
|
+
// Surface the issued serial + fingerprint so the caller can track/revoke
|
|
409
|
+
// the cert by the same identifiers without re-parsing the PEM.
|
|
410
|
+
var id = _certIdentity(result.cert);
|
|
411
|
+
return Object.assign({}, result, { serialNumber: id.serialNumber, fingerprint: id.fingerprint });
|
|
383
412
|
}
|
|
384
413
|
|
|
385
414
|
async function generateClientP12(opts2) {
|
|
@@ -395,32 +424,63 @@ function create(opts) {
|
|
|
395
424
|
throw new MtlsCaError("mtls-ca/bad-engine-output",
|
|
396
425
|
"engine.packageP12 must return { p12: Buffer, certPem, issuedAt, expiresAt }");
|
|
397
426
|
}
|
|
427
|
+
if (typeof result.certPem === "string") {
|
|
428
|
+
var id12 = _certIdentity(result.certPem);
|
|
429
|
+
return Object.assign({}, result, { serialNumber: id12.serialNumber, fingerprint: id12.fingerprint });
|
|
430
|
+
}
|
|
398
431
|
return result;
|
|
399
432
|
}
|
|
400
433
|
|
|
401
434
|
// ---- Revocation registry + CRL ----
|
|
402
435
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
436
|
+
// Revocation entries are read + written through a store so the registry can
|
|
437
|
+
// live somewhere other than the default plaintext revocations.json — e.g. an
|
|
438
|
+
// operator-supplied encrypted / clustered store (the bring-your-own-store
|
|
439
|
+
// precedent b.queue's config.db set). Contract (sync):
|
|
440
|
+
// list() -> array of revocation entries
|
|
441
|
+
// add(entry) -> append one entry (the caller has already deduped)
|
|
442
|
+
function _defaultFileStore() {
|
|
443
|
+
function _list() {
|
|
444
|
+
if (!nodeFs.existsSync(paths.revocations)) return [];
|
|
445
|
+
try {
|
|
446
|
+
// safeJson.parse caps depth + size + protects against
|
|
447
|
+
// proto-pollution; a tampered or truncated file shouldn't be able to
|
|
448
|
+
// corrupt the rotator process.
|
|
449
|
+
var json = safeJson.parse(nodeFs.readFileSync(paths.revocations, "utf8"),
|
|
450
|
+
{ maxBytes: C.BYTES.mib(16) });
|
|
451
|
+
return (json && Array.isArray(json.revocations)) ? json.revocations : [];
|
|
452
|
+
} catch (e) {
|
|
453
|
+
throw new MtlsCaError("mtls-ca/revocation-corrupt",
|
|
454
|
+
"could not parse " + paths.revocations + ": " + ((e && e.message) || String(e)));
|
|
455
|
+
}
|
|
418
456
|
}
|
|
457
|
+
return {
|
|
458
|
+
list: _list,
|
|
459
|
+
add: function (entry) {
|
|
460
|
+
var entries = _list();
|
|
461
|
+
entries.push(entry);
|
|
462
|
+
atomicFile.writeSync(paths.revocations,
|
|
463
|
+
JSON.stringify({ revocations: entries }, null, 2) + "\n", { mode: 0o600 });
|
|
464
|
+
},
|
|
465
|
+
};
|
|
419
466
|
}
|
|
467
|
+
var revocationStore = opts.revocationStore || _defaultFileStore();
|
|
468
|
+
validateOpts.requireMethods(revocationStore, ["list", "add"],
|
|
469
|
+
"opts.revocationStore", MtlsCaError, "mtls-ca/bad-revocation-store");
|
|
420
470
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
471
|
+
// A fingerprint is the SHA3-512 hex the require-mtls gate pins. Normalize it
|
|
472
|
+
// like a serial (strip 0x / separators / whitespace, lowercase, hex-validate)
|
|
473
|
+
// so a consumer can revoke by the same value the gate compares against.
|
|
474
|
+
function _normalizeFingerprint(fp) {
|
|
475
|
+
if (!fp || typeof fp !== "string") {
|
|
476
|
+
throw new MtlsCaError("mtls-ca/bad-fingerprint", "fingerprint must be a non-empty string");
|
|
477
|
+
}
|
|
478
|
+
var stripped = fp.replace(/^0x/i, "").replace(/[:\-\s]/g, "");
|
|
479
|
+
if (!safeBuffer.isHex(stripped)) {
|
|
480
|
+
throw new MtlsCaError("mtls-ca/bad-fingerprint",
|
|
481
|
+
"fingerprint contains non-hex characters: " + JSON.stringify(fp));
|
|
482
|
+
}
|
|
483
|
+
return stripped.toLowerCase();
|
|
424
484
|
}
|
|
425
485
|
|
|
426
486
|
function _normalizeSerial(s) {
|
|
@@ -466,46 +526,66 @@ function create(opts) {
|
|
|
466
526
|
"aACompromise": 10,
|
|
467
527
|
};
|
|
468
528
|
|
|
469
|
-
function revoke(
|
|
470
|
-
|
|
529
|
+
function revoke(idOrOpts, opts3) {
|
|
530
|
+
// Accept either revoke(serialString, { reason, fingerprint }) — the
|
|
531
|
+
// backward-compatible serial-keyed form — or revoke({ serial?,
|
|
532
|
+
// fingerprint?, reason? }). The require-mtls gate denies by fingerprint,
|
|
533
|
+
// so a fingerprint-indexed consumer can revoke by the same value it pins
|
|
534
|
+
// on; serial-keyed behavior stays the default. At least one key required.
|
|
535
|
+
var spec = (idOrOpts && typeof idOrOpts === "object") ? idOrOpts : null;
|
|
471
536
|
opts3 = opts3 || {};
|
|
472
|
-
var
|
|
537
|
+
var serialIn = spec ? spec.serial : idOrOpts;
|
|
538
|
+
var fingerprintIn = spec ? spec.fingerprint : opts3.fingerprint;
|
|
539
|
+
var reasonName = (spec ? spec.reason : opts3.reason) || "unspecified";
|
|
540
|
+
|
|
541
|
+
var serial = (serialIn !== undefined && serialIn !== null) ? _normalizeSerial(serialIn) : null;
|
|
542
|
+
var fingerprint = (fingerprintIn !== undefined && fingerprintIn !== null)
|
|
543
|
+
? _normalizeFingerprint(fingerprintIn) : null;
|
|
544
|
+
if (!serial && !fingerprint) {
|
|
545
|
+
throw new MtlsCaError("mtls-ca/no-revocation-key",
|
|
546
|
+
"revoke requires a serial number or a fingerprint " +
|
|
547
|
+
"(revoke(serial, opts) or revoke({ serial, fingerprint }))");
|
|
548
|
+
}
|
|
473
549
|
var reasonCode = CRL_REASON_BY_NAME[reasonName];
|
|
474
550
|
if (reasonCode === undefined) {
|
|
475
551
|
throw new MtlsCaError("mtls-ca/bad-reason",
|
|
476
552
|
"revoke: unknown reason '" + reasonName + "' (valid: " +
|
|
477
553
|
Object.keys(CRL_REASON_BY_NAME).join(", ") + ")");
|
|
478
554
|
}
|
|
479
|
-
var
|
|
480
|
-
|
|
481
|
-
return r.serialNumber === serial;
|
|
555
|
+
var existing = revocationStore.list().find(function (r) {
|
|
556
|
+
return (serial && r.serialNumber === serial) || (fingerprint && r.fingerprint === fingerprint);
|
|
482
557
|
});
|
|
483
558
|
if (existing) {
|
|
484
|
-
// Idempotent — repeated revoke() of the same serial doesn't
|
|
559
|
+
// Idempotent — repeated revoke() of the same serial/fingerprint doesn't
|
|
485
560
|
// shift the revokedAt timestamp.
|
|
486
561
|
return existing;
|
|
487
562
|
}
|
|
488
563
|
var entry = {
|
|
489
564
|
serialNumber: serial,
|
|
565
|
+
fingerprint: fingerprint,
|
|
490
566
|
reason: reasonName,
|
|
491
567
|
reasonCode: reasonCode,
|
|
492
568
|
revokedAt: Date.now(),
|
|
493
569
|
};
|
|
494
|
-
|
|
495
|
-
_saveRevocations(state);
|
|
570
|
+
revocationStore.add(entry);
|
|
496
571
|
return entry;
|
|
497
572
|
}
|
|
498
573
|
|
|
499
|
-
function isRevoked(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
574
|
+
function isRevoked(serialOrFingerprint) {
|
|
575
|
+
// Accept a serial number OR a SHA3-512 fingerprint — both are hex, so one
|
|
576
|
+
// normalized form is matched against either key each entry carries.
|
|
577
|
+
if (!serialOrFingerprint || typeof serialOrFingerprint !== "string") {
|
|
578
|
+
throw new MtlsCaError("mtls-ca/bad-revocation-key",
|
|
579
|
+
"isRevoked requires a serial number or a fingerprint (hex string)");
|
|
580
|
+
}
|
|
581
|
+
var norm = _normalizeFingerprint(serialOrFingerprint);
|
|
582
|
+
return revocationStore.list().some(function (r) {
|
|
583
|
+
return r.serialNumber === norm || r.fingerprint === norm;
|
|
504
584
|
});
|
|
505
585
|
}
|
|
506
586
|
|
|
507
587
|
function getRevocations() {
|
|
508
|
-
return
|
|
588
|
+
return revocationStore.list().slice();
|
|
509
589
|
}
|
|
510
590
|
|
|
511
591
|
// Generate a signed X.509 CRL covering every entry in the registry.
|
|
@@ -521,7 +601,7 @@ function create(opts) {
|
|
|
521
601
|
"framework's bundled CA engine, which supports it");
|
|
522
602
|
}
|
|
523
603
|
var ca = await initCA();
|
|
524
|
-
var revocations =
|
|
604
|
+
var revocations = revocationStore.list();
|
|
525
605
|
var nowMs = Date.now();
|
|
526
606
|
var thisUpdate = opts3.thisUpdate || new Date(nowMs);
|
|
527
607
|
var nextUpdate = opts3.nextUpdate ||
|