@blamejs/core 0.15.12 → 0.15.14
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 +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- 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 +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- 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 +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- 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/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- 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 +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- 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 +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- 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 +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- 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 +13 -14
- 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 +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- 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/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- 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-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- 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 +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- 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 +54 -41
- 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 +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- 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/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- 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 +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -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-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- 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 +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/auth/access-lock.js
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
var defineClass = require("../framework-error").defineClass;
|
|
42
42
|
var lazyRequire = require("../lazy-require");
|
|
43
43
|
var validateOpts = require("../validate-opts");
|
|
44
|
+
var requestHelpers = require("../request-helpers");
|
|
44
45
|
|
|
45
46
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
46
47
|
var observability = lazyRequire(function () { return require("../observability"); });
|
|
@@ -53,7 +54,7 @@ var SAFE_METHODS = Object.freeze({ GET: 1, HEAD: 1, OPTIONS: 1 });
|
|
|
53
54
|
function _normalizeMode(mode) {
|
|
54
55
|
if (typeof mode !== "string") return null;
|
|
55
56
|
var m = mode.toLowerCase();
|
|
56
|
-
return VALID_MODES
|
|
57
|
+
return Object.prototype.hasOwnProperty.call(VALID_MODES, m) ? m : null;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
function create(opts) {
|
|
@@ -77,7 +78,6 @@ function create(opts) {
|
|
|
77
78
|
}
|
|
78
79
|
var passthroughPaths = Array.isArray(opts.passthroughPaths)
|
|
79
80
|
? opts.passthroughPaths.slice() : [];
|
|
80
|
-
var auditOn = opts.audit !== false;
|
|
81
81
|
var getRole = typeof opts.getRole === "function" ? opts.getRole : null;
|
|
82
82
|
var errorMessage = typeof opts.errorMessage === "string" && opts.errorMessage.length > 0
|
|
83
83
|
? opts.errorMessage : "service in restricted access mode";
|
|
@@ -87,33 +87,11 @@ function create(opts) {
|
|
|
87
87
|
var modeSetBy = "boot";
|
|
88
88
|
var modeReason = "initial mode at boot";
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
audit().safeEmit({
|
|
94
|
-
action: "auth.access_lock." + action,
|
|
95
|
-
outcome: outcome,
|
|
96
|
-
metadata: metadata || {},
|
|
97
|
-
});
|
|
98
|
-
} catch (_e) { /* drop-silent — audit is best-effort */ }
|
|
99
|
-
}
|
|
100
|
-
function _emitMetric(verb, n, labels) {
|
|
101
|
-
try { observability().safeEvent("auth.access_lock." + verb, n || 1, labels || {}); }
|
|
102
|
-
catch (_e) { /* drop-silent */ }
|
|
103
|
-
}
|
|
90
|
+
var _emitAudit = audit().namespaced("auth.access_lock", opts.audit);
|
|
91
|
+
var _emitMetric = observability().namespaced("auth.access_lock");
|
|
104
92
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
var p = req.url || "";
|
|
108
|
-
var qpos = p.indexOf("?");
|
|
109
|
-
if (qpos !== -1) p = p.slice(0, qpos);
|
|
110
|
-
for (var i = 0; i < passthroughPaths.length; i++) {
|
|
111
|
-
var entry = passthroughPaths[i];
|
|
112
|
-
if (typeof entry === "string" && (p === entry || p.indexOf(entry + "/") === 0)) return true;
|
|
113
|
-
if (entry instanceof RegExp && entry.test(p)) return true;
|
|
114
|
-
}
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
93
|
+
var _isPassthrough = requestHelpers.makeSkipMatcher(
|
|
94
|
+
{ skipPaths: passthroughPaths }, "auth.accessLock");
|
|
117
95
|
|
|
118
96
|
function _hasUnlockRole(req) {
|
|
119
97
|
if (!getRole || unlockRoles.length === 0) return false;
|
|
@@ -186,7 +164,7 @@ function create(opts) {
|
|
|
186
164
|
if (_hasUnlockRole(req)) return next();
|
|
187
165
|
if (currentMode === "read-only") {
|
|
188
166
|
var method = (req.method || "GET").toUpperCase();
|
|
189
|
-
if (SAFE_METHODS
|
|
167
|
+
if (Object.prototype.hasOwnProperty.call(SAFE_METHODS, method)) return next();
|
|
190
168
|
_emitAudit("refused", "denied", { mode: currentMode, method: method, path: req.url });
|
|
191
169
|
_emitMetric("refused", 1, { mode: currentMode, reason: "non-safe-method" });
|
|
192
170
|
return _refuse(res, "non-safe-method-in-read-only");
|
|
@@ -343,7 +343,7 @@ function create(opts) {
|
|
|
343
343
|
var secret = opts.secret;
|
|
344
344
|
|
|
345
345
|
var providerKey = opts.provider !== undefined ? opts.provider : DEFAULT_PROVIDER;
|
|
346
|
-
if (typeof providerKey !== "string" || !PROVIDERS
|
|
346
|
+
if (typeof providerKey !== "string" || !Object.prototype.hasOwnProperty.call(PROVIDERS, providerKey)) {
|
|
347
347
|
var supported = Object.keys(PROVIDERS).join(", ");
|
|
348
348
|
throw new BotChallengeError("bot-challenge/bad-opt",
|
|
349
349
|
"provider: expected one of [" + supported + "], got " + JSON.stringify(providerKey));
|
package/lib/auth/ciba.js
CHANGED
|
@@ -452,6 +452,23 @@ function create(opts) {
|
|
|
452
452
|
});
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
// Verify an OIDC ID token (if present) via the composed inner OAuth client,
|
|
456
|
+
// returning its verified claims, or null when no id_token was supplied. A
|
|
457
|
+
// present-but-invalid id_token throws (fail-closed) so a forged token's
|
|
458
|
+
// sub/acr/amr can never be returned as trusted.
|
|
459
|
+
async function _verifyIdTokenIfPresent(idToken, auditKey, op) {
|
|
460
|
+
if (!idToken) return null;
|
|
461
|
+
try {
|
|
462
|
+
return await inner.verifyIdToken(idToken);
|
|
463
|
+
} catch (e) {
|
|
464
|
+
_emitAudit("id_token_verify_fail", "failure",
|
|
465
|
+
auditKey ? { authReqIdHash: sha3Hash(auditKey) } : {});
|
|
466
|
+
throw new AuthError("auth-ciba/id-token-invalid",
|
|
467
|
+
"ciba." + op + ": id_token failed verification: " +
|
|
468
|
+
((e && e.code) || (e && e.message) || String(e)));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
455
472
|
async function pollToken(popts) {
|
|
456
473
|
popts = popts || {};
|
|
457
474
|
if (typeof popts.authReqId !== "string" || popts.authReqId.length === 0) {
|
|
@@ -514,6 +531,12 @@ function create(opts) {
|
|
|
514
531
|
}
|
|
515
532
|
// Token issued — clear interval tracking for this authReqId.
|
|
516
533
|
_intervalState.delete(popts.authReqId);
|
|
534
|
+
// Verify the ID token (OIDC Core §3.1.3.7) via the composed inner OAuth
|
|
535
|
+
// client — it applies the create()-level issuer / clientId / JWKS /
|
|
536
|
+
// accepted-algorithms and enforces signature + iss/aud/exp. Returning an
|
|
537
|
+
// unverified id_token let a forged token's sub/acr/amr be trusted.
|
|
538
|
+
var pollClaims = await _verifyIdTokenIfPresent(rv.id_token,
|
|
539
|
+
"auth-ciba:" + popts.authReqId, "pollToken");
|
|
517
540
|
_emitAudit("token_received", "success", {
|
|
518
541
|
authReqIdHash: sha3Hash("auth-ciba:" + popts.authReqId),
|
|
519
542
|
});
|
|
@@ -521,6 +544,7 @@ function create(opts) {
|
|
|
521
544
|
return {
|
|
522
545
|
accessToken: rv.access_token || null,
|
|
523
546
|
idToken: rv.id_token || null,
|
|
547
|
+
claims: pollClaims,
|
|
524
548
|
refreshToken: rv.refresh_token || null,
|
|
525
549
|
tokenType: rv.token_type || null,
|
|
526
550
|
scope: rv.scope || null,
|
|
@@ -545,17 +569,23 @@ function create(opts) {
|
|
|
545
569
|
* - In **push** mode the body carries the full token-response
|
|
546
570
|
* object; no follow-up call needed.
|
|
547
571
|
*
|
|
572
|
+
* Async because a pushed `id_token` is verified (signature + iss/aud/exp)
|
|
573
|
+
* via the composed inner OAuth client before it is returned — the
|
|
574
|
+
* verified claims are surfaced as `claims`. A present-but-invalid
|
|
575
|
+
* id_token throws `auth-ciba/id-token-invalid` (the notification-token
|
|
576
|
+
* bearer authenticates the caller, never the token itself).
|
|
577
|
+
*
|
|
548
578
|
* @opts
|
|
549
579
|
* { body?: object } // pre-parsed body; defaults to req.body
|
|
550
580
|
*
|
|
551
581
|
* @example
|
|
552
|
-
* app.post("/ciba/notify", function (req, res) {
|
|
553
|
-
* var info = ciba.parseNotification(req, { body: req.body });
|
|
554
|
-
* // → { authReqId, accessToken, idToken, ... }
|
|
582
|
+
* app.post("/ciba/notify", async function (req, res) {
|
|
583
|
+
* var info = await ciba.parseNotification(req, { body: req.body });
|
|
584
|
+
* // → { authReqId, accessToken, idToken, claims, ... }
|
|
555
585
|
* res.statusCode = 204; res.end();
|
|
556
586
|
* });
|
|
557
587
|
*/
|
|
558
|
-
function parseNotification(req, popts) {
|
|
588
|
+
async function parseNotification(req, popts) {
|
|
559
589
|
popts = popts || {};
|
|
560
590
|
if (!req || !req.headers) {
|
|
561
591
|
throw new AuthError("auth-ciba/bad-notification-req",
|
|
@@ -605,6 +635,14 @@ function create(opts) {
|
|
|
605
635
|
throw new AuthError("auth-ciba/no-auth-req-id-in-body",
|
|
606
636
|
"ciba.parseNotification: body missing auth_req_id");
|
|
607
637
|
}
|
|
638
|
+
// Verify the pushed ID token (CIBA §10.2 / OIDC Core MUST) via the
|
|
639
|
+
// composed inner OAuth client before returning it as trusted. The
|
|
640
|
+
// notification-token bearer authenticates the CALLER, not the token; a
|
|
641
|
+
// leaked/observed notification token previously let an attacker inject
|
|
642
|
+
// arbitrary id_token claims the RP would trust on the documented
|
|
643
|
+
// "no follow-up call" push path.
|
|
644
|
+
var pushClaims = await _verifyIdTokenIfPresent(body.id_token,
|
|
645
|
+
"auth-ciba:" + body.auth_req_id, "parseNotification");
|
|
608
646
|
_emitAudit("notification_received", "success", {
|
|
609
647
|
authReqIdHash: sha3Hash("auth-ciba:" + body.auth_req_id),
|
|
610
648
|
mode: deliveryMode,
|
|
@@ -614,6 +652,7 @@ function create(opts) {
|
|
|
614
652
|
authReqId: body.auth_req_id,
|
|
615
653
|
accessToken: body.access_token || null,
|
|
616
654
|
idToken: body.id_token || null,
|
|
655
|
+
claims: pushClaims,
|
|
617
656
|
refreshToken: body.refresh_token || null,
|
|
618
657
|
tokenType: body.token_type || null,
|
|
619
658
|
scope: body.scope || null,
|
package/lib/auth/dpop.js
CHANGED
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
var nodeCrypto = require("node:crypto");
|
|
30
30
|
var bCrypto = require("../crypto");
|
|
31
31
|
var jwk = require("../jwk");
|
|
32
|
+
var jwtExternal = require("./jwt-external");
|
|
32
33
|
var safeJson = require("../safe-json");
|
|
33
34
|
var safeUrl = require("../safe-url");
|
|
34
35
|
var validateOpts = require("../validate-opts");
|
|
36
|
+
var nonceStore = require("../nonce-store");
|
|
35
37
|
var C = require("../constants");
|
|
36
38
|
var { AuthError } = require("../framework-error");
|
|
37
39
|
|
|
@@ -74,16 +76,12 @@ var REFUSED_ALGS = ["HS256", "HS384", "HS512", "none"];
|
|
|
74
76
|
|
|
75
77
|
function _b64urlEncode(buf) { return bCrypto.toBase64Url(buf); }
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
throw new AuthError("auth-dpop/bad-base64",
|
|
84
|
-
"DPoP segment is not valid base64url");
|
|
85
|
-
}
|
|
86
|
-
}
|
|
79
|
+
var _b64urlDecode = bCrypto.makeBase64UrlDecoder({
|
|
80
|
+
errorClass: AuthError,
|
|
81
|
+
code: "auth-dpop/bad-base64",
|
|
82
|
+
typeMessage: "expected base64url string",
|
|
83
|
+
badMessage: "DPoP segment is not valid base64url",
|
|
84
|
+
});
|
|
87
85
|
|
|
88
86
|
// Asymmetric key types DPoP accepts (its proof model relies on a
|
|
89
87
|
// signature, so symmetric "oct" keys are refused). AKP is the IANA key
|
|
@@ -94,7 +92,7 @@ function thumbprint(key) {
|
|
|
94
92
|
if (!key || typeof key !== "object" || typeof key.kty !== "string" || key.kty.length === 0) {
|
|
95
93
|
throw new AuthError("auth-dpop/bad-jwk", "jwk must be an object with a kty");
|
|
96
94
|
}
|
|
97
|
-
if (!DPOP_KTY
|
|
95
|
+
if (!Object.prototype.hasOwnProperty.call(DPOP_KTY, key.kty)) {
|
|
98
96
|
throw new AuthError("auth-dpop/refused-kty", "jwk.kty='" + key.kty + "' is not allowed (DPoP requires asymmetric kty)");
|
|
99
97
|
}
|
|
100
98
|
// The RFC 7638 thumbprint itself is computed by b.jwk.
|
|
@@ -128,16 +126,10 @@ function _normalizeHtu(htu) {
|
|
|
128
126
|
// Pick alg-specific node:crypto verify params. PQC algs use
|
|
129
127
|
// signWithoutAlgorithm shape (`null` algorithm).
|
|
130
128
|
function _signParamsForAlg(alg) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
if (alg === "PS384") return { hash: "sha384", padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: 48 }; // RFC 7518 PS384 salt length
|
|
136
|
-
if (alg === "PS512") return { hash: "sha512", padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: 64 }; // RFC 7518 PS512 salt length
|
|
137
|
-
if (alg === "ES256") return { hash: "sha256", dsaEncoding: "ieee-p1363" };
|
|
138
|
-
if (alg === "ES384") return { hash: "sha384", dsaEncoding: "ieee-p1363" };
|
|
139
|
-
if (alg === "ES512") return { hash: "sha512", dsaEncoding: "ieee-p1363" };
|
|
140
|
-
if (alg === "EdDSA") return { hash: null };
|
|
129
|
+
// Classical-JOSE params (RS/PS/ES/EdDSA) from the shared table; DPoP layers
|
|
130
|
+
// PQC ML-DSA-87 on top (the only verifier that accepts it).
|
|
131
|
+
var params = jwtExternal.algParams(alg);
|
|
132
|
+
if (params) return params;
|
|
141
133
|
if (alg === "ML-DSA-87") return { hash: null, pqc: true };
|
|
142
134
|
throw new AuthError("auth-dpop/unsupported-alg",
|
|
143
135
|
"alg '" + alg + "' is not supported by DPoP");
|
|
@@ -195,11 +187,11 @@ function _detectAlgFromKey(key) {
|
|
|
195
187
|
}
|
|
196
188
|
|
|
197
189
|
function _jwkToKeyObject(jwk) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
190
|
+
return bCrypto.importPublicJwk(jwk, {
|
|
191
|
+
errorClass: AuthError,
|
|
192
|
+
code: "auth-dpop/bad-jwk",
|
|
193
|
+
messagePrefix: "could not import jwk: ",
|
|
194
|
+
});
|
|
203
195
|
}
|
|
204
196
|
|
|
205
197
|
// ---- buildProof ----
|
|
@@ -356,6 +348,11 @@ async function verify(proof, opts) {
|
|
|
356
348
|
"DPoP proof declares 'crit' header — refused");
|
|
357
349
|
}
|
|
358
350
|
|
|
351
|
+
// Bind the declared alg to the embedded JWK's kty/curve before handing
|
|
352
|
+
// the self-asserted key to node:crypto — every other JWS verifier in the
|
|
353
|
+
// framework enforces this (alg-confusion family, CVE-2026-22817 class).
|
|
354
|
+
jwtExternal._assertAlgKtyMatch(header.alg, header.jwk);
|
|
355
|
+
|
|
359
356
|
// Verify signature against the embedded jwk
|
|
360
357
|
var key = _jwkToKeyObject(header.jwk);
|
|
361
358
|
var params = _signParamsForAlg(header.alg);
|
|
@@ -460,16 +457,12 @@ async function verify(proof, opts) {
|
|
|
460
457
|
"verify: replayStore", AuthError, "auth-dpop/bad-replay-store",
|
|
461
458
|
"must expose checkAndInsert(jti, expireAtMs) — use b.nonceStore.create()");
|
|
462
459
|
var expireAtMs = nowMs + iatWindowSec * C.TIME.seconds(1) * 2;
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
if (inserted === false) {
|
|
470
|
-
throw new AuthError("auth-dpop/replay",
|
|
471
|
-
"DPoP proof jti='" + payload.jti + "' has been seen before — replay refused");
|
|
472
|
-
}
|
|
460
|
+
await nonceStore.enforceReplay(opts.replayStore, payload.jti, expireAtMs, {
|
|
461
|
+
errorClass: AuthError,
|
|
462
|
+
storeFailedCode: "auth-dpop/replay-store-failed",
|
|
463
|
+
replayCode: "auth-dpop/replay",
|
|
464
|
+
tokenLabel: "DPoP proof",
|
|
465
|
+
});
|
|
473
466
|
}
|
|
474
467
|
|
|
475
468
|
return { header: header, payload: payload, jkt: jkt };
|
package/lib/auth/fido-mds3.js
CHANGED
|
@@ -43,6 +43,8 @@ var safeJson = require("../safe-json");
|
|
|
43
43
|
var safeBuffer = require("../safe-buffer");
|
|
44
44
|
var lazyRequire = require("../lazy-require");
|
|
45
45
|
var validateOpts = require("../validate-opts");
|
|
46
|
+
var x509Chain = require("../x509-chain");
|
|
47
|
+
var jwtExternal = require("./jwt-external");
|
|
46
48
|
var _wa = require("../vendor/simplewebauthn-server.cjs");
|
|
47
49
|
var { FidoMds3Error } = require("../framework-error");
|
|
48
50
|
|
|
@@ -141,21 +143,14 @@ function _parseJws(token) {
|
|
|
141
143
|
// in practice; PS* and EdDSA are listed for completeness so future
|
|
142
144
|
// BLOBs over the same surface validate without a code edit.
|
|
143
145
|
function _verifyParamsForAlg(alg) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
case "ES384": return { hash: "sha384", dsaEncoding: "ieee-p1363" };
|
|
153
|
-
case "ES512": return { hash: "sha512", dsaEncoding: "ieee-p1363" };
|
|
154
|
-
case "EdDSA": return { hash: null };
|
|
155
|
-
default:
|
|
156
|
-
throw new FidoMds3Error("fido-mds3/unsupported-alg",
|
|
157
|
-
"JWS alg '" + alg + "' is not supported");
|
|
158
|
-
}
|
|
146
|
+
// Classical-JOSE alg→crypto params (RS/PS/ES/EdDSA) shared with the rest of
|
|
147
|
+
// the JOSE verifiers — jwtExternal.algParams owns the single table.
|
|
148
|
+
var params = jwtExternal.algParams(alg);
|
|
149
|
+
if (!params) {
|
|
150
|
+
throw new FidoMds3Error("fido-mds3/unsupported-alg",
|
|
151
|
+
"JWS alg '" + alg + "' is not supported");
|
|
152
|
+
}
|
|
153
|
+
return params;
|
|
159
154
|
}
|
|
160
155
|
|
|
161
156
|
// Resolve the trust roots. Operator override via caCertificate lets
|
|
@@ -245,7 +240,10 @@ function _validateChain(x5c, rootPems) {
|
|
|
245
240
|
var root;
|
|
246
241
|
try { root = new nodeCrypto.X509Certificate(rootPems[r]); }
|
|
247
242
|
catch (_e) { continue; }
|
|
248
|
-
|
|
243
|
+
// issuerValidlyIssued enforces basicConstraints cA:TRUE on the root in
|
|
244
|
+
// addition to the issuance + signature linkage — a non-CA cert cannot
|
|
245
|
+
// anchor the x5c chain (basicConstraints bypass, CVE-2002-0862 class).
|
|
246
|
+
if (x509Chain.issuerValidlyIssued(root, tail)) {
|
|
249
247
|
anchored = true;
|
|
250
248
|
break;
|
|
251
249
|
}
|
package/lib/auth/jwt-external.js
CHANGED
|
@@ -117,7 +117,16 @@ function _b64urlDecode(s) {
|
|
|
117
117
|
// supplied alg the key can't actually produce.
|
|
118
118
|
var _EC_CURVE_ALG = { prime256v1: "ES256", secp384r1: "ES384", secp521r1: "ES512" };
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
// algParams — JOSE alg → node:crypto sign/verify parameters (hash + RSA
|
|
121
|
+
// PKCS1 padding / RSASSA-PSS saltLength / ECDSA dsaEncoding). The classical-
|
|
122
|
+
// JOSE table (RS/PS/ES per RFC 7518 §3, EdDSA per RFC 8037) lives HERE once,
|
|
123
|
+
// in the classical-JOSE domain owner: dpop proofs, fido-mds3 MDS3 BLOBs,
|
|
124
|
+
// oauth ID-token verify, and this module's own sign/verify all read the
|
|
125
|
+
// identical mapping. Returns null for an unrecognised alg so every caller
|
|
126
|
+
// throws its OWN typed error and keeps its OWN supported set — oauth omits
|
|
127
|
+
// EdDSA, dpop layers the PQC ML-DSA-87 on top. The values are sign/verify-
|
|
128
|
+
// symmetric (node:crypto uses the same params for both).
|
|
129
|
+
function algParams(alg) {
|
|
121
130
|
if (alg === "RS256") return { hash: "sha256", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
122
131
|
if (alg === "RS384") return { hash: "sha384", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
123
132
|
if (alg === "RS512") return { hash: "sha512", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
@@ -128,8 +137,16 @@ function _verifyParamsForAlg(alg) {
|
|
|
128
137
|
if (alg === "ES384") return { hash: "sha384", dsaEncoding: "ieee-p1363" };
|
|
129
138
|
if (alg === "ES512") return { hash: "sha512", dsaEncoding: "ieee-p1363" };
|
|
130
139
|
if (alg === "EdDSA") return { hash: null };
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function _verifyParamsForAlg(alg) {
|
|
144
|
+
var params = algParams(alg);
|
|
145
|
+
if (!params) {
|
|
146
|
+
throw new AuthError("auth-jwt-external/unsupported-alg",
|
|
147
|
+
"alg '" + alg + "' is not supported by verifyExternal");
|
|
148
|
+
}
|
|
149
|
+
return params;
|
|
133
150
|
}
|
|
134
151
|
|
|
135
152
|
// _toPrivateKey — import the operator's classical signing key from any of
|
|
@@ -231,11 +248,11 @@ function _signCompactJws(header, payload, privateKey, alg) {
|
|
|
231
248
|
}
|
|
232
249
|
|
|
233
250
|
function _jwkToKey(jwk) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
251
|
+
return bCrypto.importPublicJwk(jwk, {
|
|
252
|
+
errorClass: AuthError,
|
|
253
|
+
code: "auth-jwt-external/bad-jwk",
|
|
254
|
+
messagePrefix: "could not import JWK (kid=" + (jwk && jwk.kid) + "): ",
|
|
255
|
+
});
|
|
239
256
|
}
|
|
240
257
|
|
|
241
258
|
// CVE-2026-22817 — RS256→HS256 alg-confusion + the broader alg/kty
|
|
@@ -725,6 +742,7 @@ module.exports = {
|
|
|
725
742
|
REFUSED_ALGS: REFUSED_ALGS,
|
|
726
743
|
// Shared JOSE defenses — routed from oauth.verifyIdToken /
|
|
727
744
|
// oid4vci proof verify / sd-jwt-vc.verify / openid-federation.
|
|
745
|
+
algParams: algParams,
|
|
728
746
|
_assertAlgKtyMatch: _assertAlgKtyMatch,
|
|
729
747
|
_issuerMatches: _issuerMatches,
|
|
730
748
|
// Classical-JWS signer internals — composed by oauth.js's attestation
|
package/lib/auth/jwt.js
CHANGED
|
@@ -78,6 +78,7 @@ var C = require("../constants");
|
|
|
78
78
|
var bCrypto = require("../crypto");
|
|
79
79
|
var safeJson = require("../safe-json");
|
|
80
80
|
var validateOpts = require("../validate-opts");
|
|
81
|
+
var nonceStore = require("../nonce-store");
|
|
81
82
|
var { AuthError } = require("../framework-error");
|
|
82
83
|
|
|
83
84
|
// Algorithm registry. The string keys are the JWT header `alg` values
|
|
@@ -92,11 +93,12 @@ var SUPPORTED_ALGORITHMS = Object.freeze(Object.keys(ALGORITHM_TO_NODE));
|
|
|
92
93
|
|
|
93
94
|
function _b64urlEncode(buf) { return bCrypto.toBase64Url(buf); }
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
var _b64urlDecode = bCrypto.makeBase64UrlDecoder({
|
|
97
|
+
errorClass: AuthError,
|
|
98
|
+
code: "auth-jwt/malformed",
|
|
99
|
+
typeMessage: "expected base64url string",
|
|
100
|
+
badMessage: "JWT segment is not valid base64url",
|
|
101
|
+
});
|
|
100
102
|
|
|
101
103
|
function _toKeyObject(pemOrKey, kind) {
|
|
102
104
|
// kind is 'private' or 'public' — passes through if already a KeyObject;
|
|
@@ -115,7 +117,7 @@ function _toKeyObject(pemOrKey, kind) {
|
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
function _resolveAlgorithm(alg) {
|
|
118
|
-
if (typeof alg !== "string" || !ALGORITHM_TO_NODE
|
|
120
|
+
if (typeof alg !== "string" || !Object.prototype.hasOwnProperty.call(ALGORITHM_TO_NODE, alg)) {
|
|
119
121
|
throw new AuthError("auth-jwt/unsupported-algorithm",
|
|
120
122
|
"algorithm must be one of " + SUPPORTED_ALGORITHMS.join(", ") + " (got: " + alg + ")");
|
|
121
123
|
}
|
|
@@ -220,7 +222,7 @@ async function verify(token, opts) {
|
|
|
220
222
|
// Validate the allowlist itself — typoed entries should surface here,
|
|
221
223
|
// not as silent "every token rejected."
|
|
222
224
|
for (var i = 0; i < allowed.length; i++) {
|
|
223
|
-
if (!ALGORITHM_TO_NODE
|
|
225
|
+
if (!Object.prototype.hasOwnProperty.call(ALGORITHM_TO_NODE, allowed[i])) {
|
|
224
226
|
throw new AuthError("auth-jwt/unsupported-algorithm",
|
|
225
227
|
"opts.algorithms[" + i + "] = '" + allowed[i] + "' is not in the supported list (" +
|
|
226
228
|
SUPPORTED_ALGORITHMS.join(", ") + ")");
|
|
@@ -409,16 +411,12 @@ async function verify(token, opts) {
|
|
|
409
411
|
if (typeof p.exp === "number") {
|
|
410
412
|
expireAtMs = p.exp * C.TIME.seconds(1);
|
|
411
413
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
if (inserted === false) {
|
|
419
|
-
throw new AuthError("auth-jwt/replay",
|
|
420
|
-
"token jti='" + p.jti + "' has been seen before — replay refused");
|
|
421
|
-
}
|
|
414
|
+
await nonceStore.enforceReplay(opts.replayStore, p.jti, expireAtMs, {
|
|
415
|
+
errorClass: AuthError,
|
|
416
|
+
storeFailedCode: "auth-jwt/replay-store-failed",
|
|
417
|
+
replayCode: "auth-jwt/replay",
|
|
418
|
+
tokenLabel: "token",
|
|
419
|
+
});
|
|
422
420
|
}
|
|
423
421
|
|
|
424
422
|
return p;
|
package/lib/auth/lockout.js
CHANGED
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
*/
|
|
81
81
|
|
|
82
82
|
var C = require("../constants");
|
|
83
|
+
var numericBounds = require("../numeric-bounds");
|
|
83
84
|
var lazyRequire = require("../lazy-require");
|
|
84
85
|
var requestHelpers = require("../request-helpers");
|
|
85
86
|
var validateOpts = require("../validate-opts");
|
|
@@ -119,7 +120,7 @@ function _requireString(name, val) {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
function _requirePositiveInt(name, val) {
|
|
122
|
-
if (
|
|
123
|
+
if (!numericBounds.isPositiveFiniteInt(val)) {
|
|
123
124
|
throw _err("BAD_OPT", name + ": expected positive integer, got " +
|
|
124
125
|
typeof val + " " + JSON.stringify(val));
|
|
125
126
|
}
|
|
@@ -200,32 +201,13 @@ function create(opts) {
|
|
|
200
201
|
|
|
201
202
|
function _scopedKey(key) { return namespace + ":" + key; }
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
try { sink.event(name, 1, labels); } catch (_e) { /* observability is best-effort */ }
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Fall back to the framework's global observability registry when the
|
|
210
|
-
// operator didn't pass their own. event() is a no-op when no registry
|
|
211
|
-
// is wired, so this is zero-cost in apps without observability.
|
|
212
|
-
function _safeGlobalObs() {
|
|
213
|
-
try { return observability(); } catch (_e) { return null; }
|
|
214
|
-
}
|
|
204
|
+
// Emit to the operator's configured observability instance, else the
|
|
205
|
+
// framework's global registry (a no-op when none is wired), drop-silent.
|
|
206
|
+
var _emitObs = observability().makeCounterEmitter(obsInst);
|
|
215
207
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
var event = {
|
|
220
|
-
action: action,
|
|
221
|
-
outcome: outcome,
|
|
222
|
-
resource: { kind: "auth.lockout", id: namespace + ":" + key },
|
|
223
|
-
metadata: metadata || {},
|
|
224
|
-
};
|
|
225
|
-
if (req) event.actor = requestHelpers.extractActorContext(req);
|
|
226
|
-
auditInst.safeEmit(event);
|
|
227
|
-
} catch (_e) { /* audit best-effort */ }
|
|
228
|
-
}
|
|
208
|
+
var _emitAudit = requestHelpers.makeResourceAuditEmitter(auditInst, "auth.lockout", function (key) {
|
|
209
|
+
return namespace + ":" + key;
|
|
210
|
+
});
|
|
229
211
|
|
|
230
212
|
// Cache failures fail-OPEN by design (per the framework's
|
|
231
213
|
// documented brute-force-lockout posture — rather than crash the
|
|
@@ -280,7 +262,22 @@ function create(opts) {
|
|
|
280
262
|
|
|
281
263
|
// ---- Public surface ----
|
|
282
264
|
|
|
283
|
-
|
|
265
|
+
// Per-key serialization of the failure counter (read→increment→write on an
|
|
266
|
+
// async store): concurrent recordFailure calls for the same key would lose
|
|
267
|
+
// updates, letting parallel failures stay under the lockout threshold. A
|
|
268
|
+
// per-key promise chain applies them sequentially in-process.
|
|
269
|
+
var _recordChains = new Map();
|
|
270
|
+
function recordFailure(key, callOpts) {
|
|
271
|
+
var prev = _recordChains.get(key) || Promise.resolve();
|
|
272
|
+
var run = prev.then(function () { return _doRecordFailure(key, callOpts); },
|
|
273
|
+
function () { return _doRecordFailure(key, callOpts); });
|
|
274
|
+
var tail = run.then(function () {}, function () {});
|
|
275
|
+
_recordChains.set(key, tail);
|
|
276
|
+
tail.then(function () { if (_recordChains.get(key) === tail) _recordChains.delete(key); });
|
|
277
|
+
return run;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function _doRecordFailure(key, callOpts) {
|
|
284
281
|
_requireKey(key);
|
|
285
282
|
callOpts = callOpts || {};
|
|
286
283
|
var now = clock();
|