@blamejs/blamejs-shop 0.5.19 → 0.5.21
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/README.md +1 -0
- package/SECURITY.md +17 -0
- package/lib/addresses.js +2 -2
- package/lib/admin.js +49 -45
- package/lib/affiliates.js +7 -23
- package/lib/announcement-bar.js +5 -22
- package/lib/api-keys.js +4 -13
- package/lib/asset-manifest.json +1 -1
- package/lib/banner-ab-tests.js +5 -9
- package/lib/blog-articles.js +8 -26
- package/lib/captcha-gate.js +7 -17
- package/lib/carrier-accounts.js +3 -10
- package/lib/catalog-drafts.js +5 -12
- package/lib/category-navigation.js +4 -20
- package/lib/cms-blocks.js +4 -7
- package/lib/compliance-export.js +4 -11
- package/lib/consent-ledger.js +2 -4
- package/lib/cost-layers.js +56 -21
- package/lib/customer-import.js +2 -2
- package/lib/customer-notes.js +7 -23
- package/lib/customer-roles.js +3 -5
- package/lib/customer-segments.js +17 -14
- package/lib/customer-surveys.js +7 -14
- package/lib/customers.js +2 -4
- package/lib/dispute-resolution.js +5 -13
- package/lib/dropship-forwarding.js +3 -5
- package/lib/dunning.js +2 -7
- package/lib/email-ab-tests.js +4 -8
- package/lib/email-campaigns.js +25 -14
- package/lib/email-warmup.js +2 -7
- package/lib/experiments.js +6 -10
- package/lib/externaldb-d1.js +78 -44
- package/lib/geolocation.js +18 -28
- package/lib/gift-options.js +5 -23
- package/lib/knowledge-base.js +7 -22
- package/lib/line-gift-wrap.js +3 -17
- package/lib/live-chat.js +5 -18
- package/lib/metered-usage.js +3 -8
- package/lib/notifications.js +3 -7
- package/lib/operator-accounts.js +3 -5
- package/lib/operator-approvals.js +6 -8
- package/lib/operator-audit-log.js +2 -7
- package/lib/operator-help-center.js +5 -21
- package/lib/operator-inbox.js +4 -11
- package/lib/operator-roles.js +5 -7
- package/lib/order-escalation.js +2 -2
- package/lib/order-export.js +24 -20
- package/lib/order-notes.js +4 -14
- package/lib/order-ratings.js +4 -10
- package/lib/payment-methods.js +5 -13
- package/lib/payment-retries.js +2 -7
- package/lib/payment.js +103 -58
- package/lib/pixel-events.js +8 -23
- package/lib/plan-changes.js +2 -7
- package/lib/product-qa.js +4 -8
- package/lib/promo-banners.js +7 -15
- package/lib/purchase-orders.js +3 -6
- package/lib/push-notifications.js +7 -32
- package/lib/pwa-manifest.js +6 -11
- package/lib/quotes.js +5 -9
- package/lib/referrals.js +10 -6
- package/lib/reviews.js +4 -10
- package/lib/robots-config.js +6 -14
- package/lib/sales-tax-filings.js +2 -2
- package/lib/search-facets.js +5 -5
- package/lib/search-ranking.js +4 -7
- package/lib/search-synonyms.js +7 -21
- package/lib/security-middleware.js +125 -70
- package/lib/seller-signup.js +10 -26
- package/lib/sidebar-widgets.js +6 -15
- package/lib/site-redirects.js +49 -137
- package/lib/sitemap-generator.js +3 -7
- package/lib/sms-dispatcher.js +7 -23
- package/lib/stock-receipts.js +4 -10
- package/lib/storefront-dashboards.js +2 -10
- package/lib/storefront-forms.js +9 -22
- package/lib/storefront-pages.js +6 -20
- package/lib/storefront.js +21 -23
- package/lib/subscription-billing.js +3 -8
- package/lib/subscription-controls.js +2 -7
- package/lib/suggestion-box.js +9 -26
- package/lib/support-tickets.js +6 -24
- package/lib/tax-remittance.js +2 -2
- package/lib/tenants.js +2 -7
- package/lib/text-guard.js +137 -35
- package/lib/theme-assets.js +4 -16
- package/lib/translations.js +3 -6
- package/lib/trust-badges.js +4 -7
- package/lib/vendor/MANIFEST.json +107 -97
- package/lib/vendor/blamejs/CHANGELOG.md +180 -0
- package/lib/vendor/blamejs/NOTICE +14 -10
- package/lib/vendor/blamejs/README.md +17 -5
- package/lib/vendor/blamejs/index.js +2 -0
- package/lib/vendor/blamejs/lib/acme.js +9 -9
- package/lib/vendor/blamejs/lib/api-snapshot.js +35 -16
- package/lib/vendor/blamejs/lib/app-shutdown.js +1 -1
- package/lib/vendor/blamejs/lib/archive-adapters.js +1 -1
- package/lib/vendor/blamejs/lib/archive.js +13 -24
- package/lib/vendor/blamejs/lib/auth/access-lock.js +2 -1
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +3 -6
- package/lib/vendor/blamejs/lib/auth/ciba.js +32 -3
- package/lib/vendor/blamejs/lib/auth/oauth.js +65 -8
- package/lib/vendor/blamejs/lib/auth/password.js +1 -1
- package/lib/vendor/blamejs/lib/auth/saml.js +21 -3
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +3 -3
- package/lib/vendor/blamejs/lib/backup/index.js +5 -5
- package/lib/vendor/blamejs/lib/case-fold-classes.js +491 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +131 -0
- package/lib/vendor/blamejs/lib/constants.js +79 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +8 -2
- package/lib/vendor/blamejs/lib/error-page.js +20 -1
- package/lib/vendor/blamejs/lib/external-db.js +5 -1
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +24 -6
- package/lib/vendor/blamejs/lib/flag.js +28 -2
- package/lib/vendor/blamejs/lib/framework-schema.js +14 -3
- package/lib/vendor/blamejs/lib/guard-regex.js +2345 -89
- package/lib/vendor/blamejs/lib/http-client.js +217 -40
- package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +3 -2
- package/lib/vendor/blamejs/lib/mail-dav.js +26 -26
- package/lib/vendor/blamejs/lib/mail-deploy.js +10 -10
- package/lib/vendor/blamejs/lib/mail-scan.js +3 -0
- package/lib/vendor/blamejs/lib/mail-store.js +7 -1
- package/lib/vendor/blamejs/lib/mail.js +5 -1
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/compression.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/cors.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/deny-response.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +2 -2
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +17 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +10 -8
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +4 -3
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +3 -2
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +2 -1
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +2 -1
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +1 -1
- package/lib/vendor/blamejs/lib/network-dns.js +3 -3
- package/lib/vendor/blamejs/lib/network-nts.js +35 -14
- package/lib/vendor/blamejs/lib/network-proxy.js +42 -4
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +1 -1
- package/lib/vendor/blamejs/lib/network-tls.js +341 -11
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +2 -2
- package/lib/vendor/blamejs/lib/ntp-check.js +30 -19
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/gcs.js +1 -1
- package/lib/vendor/blamejs/lib/object-store/http-put.js +2 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -5
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +2 -2
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +4 -2
- package/lib/vendor/blamejs/lib/openapi.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +4 -7
- package/lib/vendor/blamejs/lib/problem-details.js +2 -1
- package/lib/vendor/blamejs/lib/redis-client.js +12 -1
- package/lib/vendor/blamejs/lib/regex-linear.js +1349 -0
- package/lib/vendor/blamejs/lib/render.js +531 -7
- package/lib/vendor/blamejs/lib/request-helpers.js +181 -5
- package/lib/vendor/blamejs/lib/router.js +37 -13
- package/lib/vendor/blamejs/lib/safe-async.js +142 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +3 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +15 -3
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/testing.js +54 -7
- package/lib/vendor/blamejs/lib/validate-opts.js +56 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +62 -25
- package/lib/vendor/blamejs/lib/vendor/blamejs-pki.cjs +2004 -142
- package/lib/vendor/blamejs/lib/vendor/browser/noble-ciphers.mjs +727 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-hashes.mjs +1070 -0
- package/lib/vendor/blamejs/lib/vendor/browser/noble-post-quantum.mjs +1090 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +510 -227
- package/lib/vendor/blamejs/lib/webhook-hmac.js +19 -2
- package/lib/vendor/blamejs/lib/webhook.js +1 -1
- package/lib/vendor/blamejs/lib/ws-client.js +24 -2
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/sbom.cdx.json +6 -6
- package/lib/vendor-invoices.js +3 -6
- package/lib/vendors.js +5 -19
- package/lib/webhook-receiver.js +46 -83
- package/lib/webhook-subscriptions.js +3 -13
- package/lib/wishlist-sharing.js +5 -10
- package/package.json +1 -1
|
@@ -38,9 +38,11 @@
|
|
|
38
38
|
* and internal helpers.
|
|
39
39
|
*
|
|
40
40
|
* Function-arity changes: a DECREASE in `fn.length` is breaking
|
|
41
|
-
* (the operator removed a required parameter). An INCREASE is
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* (the operator removed a required parameter). An INCREASE is
|
|
42
|
+
* additive rather than breaking — a trailing optional parameter
|
|
43
|
+
* cannot break an existing caller — but it IS reported, because the
|
|
44
|
+
* baseline records arity: leaving it silent means the committed
|
|
45
|
+
* snapshot goes stale while the gate prints "no changes".
|
|
44
46
|
*
|
|
45
47
|
* On-disk format: stable canonical JSON ordered as
|
|
46
48
|
* `{ version, frameworkVersion, createdAt, exports }`. The format
|
|
@@ -310,18 +312,30 @@ function _walkCompare(oldNode, newNode, prefix, breaking, additive, typeChanged)
|
|
|
310
312
|
return;
|
|
311
313
|
}
|
|
312
314
|
|
|
313
|
-
// For function nodes, arity DROPS are
|
|
314
|
-
//
|
|
315
|
-
// optional
|
|
315
|
+
// For function nodes, arity DROPS are breaking (a required parameter was
|
|
316
|
+
// removed). An INCREASE cannot break a caller — the added parameter is
|
|
317
|
+
// trailing and optional — but the baseline records arity, so a silent pass
|
|
318
|
+
// leaves it stale while the gate prints "no changes": exactly how a changed
|
|
319
|
+
// signature reaches a release untracked. It is reported as additive, the
|
|
320
|
+
// list that already means "not a failure, refresh the baseline".
|
|
316
321
|
if (oldNode.type === "function") {
|
|
317
|
-
if (typeof oldNode.arity === "number" && typeof newNode.arity === "number"
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
322
|
+
if (typeof oldNode.arity === "number" && typeof newNode.arity === "number") {
|
|
323
|
+
if (newNode.arity < oldNode.arity) {
|
|
324
|
+
breaking.push({
|
|
325
|
+
path: prefix,
|
|
326
|
+
kind: "arity-decreased",
|
|
327
|
+
was: "function/" + oldNode.arity,
|
|
328
|
+
is: "function/" + newNode.arity,
|
|
329
|
+
});
|
|
330
|
+
} else if (newNode.arity > oldNode.arity) {
|
|
331
|
+
additive.push({
|
|
332
|
+
path: prefix,
|
|
333
|
+
type: "function",
|
|
334
|
+
kind: "arity-increased",
|
|
335
|
+
was: "function/" + oldNode.arity,
|
|
336
|
+
is: "function/" + newNode.arity,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
325
339
|
}
|
|
326
340
|
return;
|
|
327
341
|
}
|
|
@@ -367,7 +381,9 @@ function _walkCompare(oldNode, newNode, prefix, breaking, additive, typeChanged)
|
|
|
367
381
|
* member that was removed, retyped, lost arity, swapped its
|
|
368
382
|
* constructor name, or changed primitive `valueType`; the release
|
|
369
383
|
* workflow exits non-zero when this list is non-empty. `additive`
|
|
370
|
-
* lists new members
|
|
384
|
+
* lists new members AND functions that gained a trailing parameter
|
|
385
|
+
* (informational — neither breaks a caller, but both move the
|
|
386
|
+
* surface the baseline records, so the operator should rerun
|
|
371
387
|
* `capture` and commit the refreshed baseline). `typeChanged` is a
|
|
372
388
|
* subset of `breaking` surfaced separately for easier triage.
|
|
373
389
|
*
|
|
@@ -446,7 +462,10 @@ function formatDiff(diff) {
|
|
|
446
462
|
lines.push("[api-snapshot] additive (" + diff.additive.length + ", informational):");
|
|
447
463
|
for (var j = 0; j < diff.additive.length; j++) {
|
|
448
464
|
var a = diff.additive[j];
|
|
449
|
-
|
|
465
|
+
var aline = " + " + a.path + " (" + (a.kind || a.type) + ")";
|
|
466
|
+
if (a.was !== undefined) aline += " was=" + JSON.stringify(a.was);
|
|
467
|
+
if (a.is !== undefined) aline += " is=" + JSON.stringify(a.is);
|
|
468
|
+
lines.push(aline);
|
|
450
469
|
}
|
|
451
470
|
}
|
|
452
471
|
return lines.join("\n");
|
|
@@ -162,7 +162,7 @@ function create(opts) {
|
|
|
162
162
|
// don't get a stale 503 served from a CDN.
|
|
163
163
|
if (typeof res.writeHead === "function") {
|
|
164
164
|
var body = JSON.stringify({ error: "service-shutting-down" });
|
|
165
|
-
res.writeHead(
|
|
165
|
+
res.writeHead(C.HTTP.STATUS.SERVICE_UNAVAILABLE, {
|
|
166
166
|
"Content-Type": "application/json; charset=utf-8",
|
|
167
167
|
"Content-Length": Buffer.byteLength(body),
|
|
168
168
|
"Cache-Control": "no-store",
|
|
@@ -472,7 +472,7 @@ function http(url, opts) {
|
|
|
472
472
|
signal: signal,
|
|
473
473
|
audit: opts.audit,
|
|
474
474
|
});
|
|
475
|
-
if (!res || (res.status !==
|
|
475
|
+
if (!res || (res.status !== C.HTTP.STATUS.PARTIAL_CONTENT && res.status !== C.HTTP.STATUS.OK)) {
|
|
476
476
|
throw new AdapterError("adapter/bad-response",
|
|
477
477
|
"http.range: expected 206 Partial Content, got " + (res && res.status));
|
|
478
478
|
}
|
|
@@ -61,6 +61,7 @@ var C = require("./constants");
|
|
|
61
61
|
var { defineClass } = require("./framework-error");
|
|
62
62
|
var auditEmit = require("./audit-emit");
|
|
63
63
|
var atomicFile = require("./atomic-file");
|
|
64
|
+
var safeAsync = require("./safe-async");
|
|
64
65
|
var safeBuffer = require("./safe-buffer");
|
|
65
66
|
|
|
66
67
|
var ArchiveError = defineClass("ArchiveError", { alwaysPermanent: true });
|
|
@@ -492,25 +493,12 @@ function zip() {
|
|
|
492
493
|
|
|
493
494
|
var _emitAudit = auditEmit.emitToSink; // operator-sink audit emit (opts.audit)
|
|
494
495
|
|
|
496
|
+
// The drain-aware write is b.safeAsync.writeChunk — the same one
|
|
497
|
+
// b.render.stream uses. It was private here first; a second copy would be a
|
|
498
|
+
// second chance to get the closed-peer case wrong, which is the half that
|
|
499
|
+
// hangs a request rather than failing it.
|
|
495
500
|
function _writeChunk(writable, chunk) {
|
|
496
|
-
return
|
|
497
|
-
function onError(e) {
|
|
498
|
-
writable.removeListener("drain", onDrain);
|
|
499
|
-
reject(e);
|
|
500
|
-
}
|
|
501
|
-
function onDrain() {
|
|
502
|
-
writable.removeListener("error", onError);
|
|
503
|
-
resolve();
|
|
504
|
-
}
|
|
505
|
-
var ok = writable.write(chunk);
|
|
506
|
-
if (ok) {
|
|
507
|
-
// Already flushed — no need to wait for drain.
|
|
508
|
-
resolve();
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
writable.once("drain", onDrain);
|
|
512
|
-
writable.once("error", onError);
|
|
513
|
-
});
|
|
501
|
+
return safeAsync.writeChunk(writable, chunk);
|
|
514
502
|
}
|
|
515
503
|
|
|
516
504
|
async function _streamEntry(entry, writable) {
|
|
@@ -552,9 +540,12 @@ function zip() {
|
|
|
552
540
|
var sinkWritable = new nodeStream.Writable({
|
|
553
541
|
write: function (chunk, enc, cb) {
|
|
554
542
|
csize += chunk.length;
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
543
|
+
// Through the shared writer, so a destination that goes away
|
|
544
|
+
// mid-entry fails the pipeline instead of leaving `cb` uncalled.
|
|
545
|
+
// Waiting only for `drain` here meant a destroyed sink never called
|
|
546
|
+
// back at all: the pipeline never settled, toStream() never settled,
|
|
547
|
+
// and the listener stayed attached to the dead stream.
|
|
548
|
+
safeAsync.writeChunk(writable, chunk).then(function () { cb(); }, cb);
|
|
558
549
|
},
|
|
559
550
|
});
|
|
560
551
|
try {
|
|
@@ -568,9 +559,7 @@ function zip() {
|
|
|
568
559
|
var storeCollect = new nodeStream.Writable({
|
|
569
560
|
write: function (chunk, enc, cb) {
|
|
570
561
|
csize += chunk.length;
|
|
571
|
-
|
|
572
|
-
if (ok) cb();
|
|
573
|
-
else writable.once("drain", function () { cb(); });
|
|
562
|
+
safeAsync.writeChunk(writable, chunk).then(function () { cb(); }, cb);
|
|
574
563
|
},
|
|
575
564
|
});
|
|
576
565
|
try {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
* });
|
|
41
41
|
*/
|
|
42
42
|
|
|
43
|
+
var C = require("../constants");
|
|
43
44
|
var defineClass = require("../framework-error").defineClass;
|
|
44
45
|
var lazyRequire = require("../lazy-require");
|
|
45
46
|
var validateOpts = require("../validate-opts");
|
|
@@ -147,7 +148,7 @@ function create(opts) {
|
|
|
147
148
|
|
|
148
149
|
function _refuse(res, reason) {
|
|
149
150
|
if (!res.writableEnded && typeof res.writeHead === "function") {
|
|
150
|
-
res.writeHead(
|
|
151
|
+
res.writeHead(C.HTTP.STATUS.SERVICE_UNAVAILABLE, {
|
|
151
152
|
"Content-Type": "application/json; charset=utf-8",
|
|
152
153
|
"Retry-After": "60",
|
|
153
154
|
"Cache-Control": "no-store",
|
|
@@ -227,12 +227,9 @@ function _normaliseAllowlist(name, val) {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
function _httpClientShape(client, callerLabel) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
callerLabel + ": httpClient must be a b.httpClient-shaped object (request fn)");
|
|
234
|
-
}
|
|
235
|
-
return client;
|
|
230
|
+
return validateOpts.optionalObjectWithMethod(client, "request",
|
|
231
|
+
callerLabel + ": httpClient", BotChallengeError, "bot-challenge/bad-opt",
|
|
232
|
+
"must be a b.httpClient-shaped object (request fn)") || null;
|
|
236
233
|
}
|
|
237
234
|
|
|
238
235
|
function _normaliseTimeoutMs(val) {
|
|
@@ -103,7 +103,8 @@ var _emitMetric = emit.metric;
|
|
|
103
103
|
* scope?: string|string[],
|
|
104
104
|
* deliveryMode: "poll"|"ping"|"push",
|
|
105
105
|
* clientNotificationToken?: string, // fixed token RP mints once + registers with IdP
|
|
106
|
-
* httpClientOpts?: object,
|
|
106
|
+
* httpClientOpts?: object, // options merged INTO each request
|
|
107
|
+
* http?: { client?: object, allowedHosts?: string[] }, // the client each request is made THROUGH, and its host pin
|
|
107
108
|
* allowHttp?: boolean,
|
|
108
109
|
* }
|
|
109
110
|
*
|
|
@@ -144,6 +145,13 @@ function create(opts) {
|
|
|
144
145
|
"auth.ciba.client.create: clientAssertionSigner required for clientAuth='jwt'");
|
|
145
146
|
}
|
|
146
147
|
|
|
148
|
+
// The client this flow dials with, and the host pin every dial carries —
|
|
149
|
+
// shared with the inner OAuth client below so a CIBA flow does not split
|
|
150
|
+
// across two clients, with the backchannel POST inside an operator's
|
|
151
|
+
// instrumentation and discovery outside it.
|
|
152
|
+
var httpOpts = validateOpts.outboundHttpOpts(opts.http, "auth.ciba.client.create",
|
|
153
|
+
AuthError, "auth-ciba");
|
|
154
|
+
|
|
147
155
|
var deliveryMode = opts.deliveryMode || "poll";
|
|
148
156
|
if (["poll", "ping", "push"].indexOf(deliveryMode) === -1) {
|
|
149
157
|
throw new AuthError("auth-ciba/bad-delivery-mode",
|
|
@@ -162,7 +170,13 @@ function create(opts) {
|
|
|
162
170
|
scope: opts.scope,
|
|
163
171
|
backchannelAuthenticationEndpoint: opts.backchannelAuthenticationEndpoint,
|
|
164
172
|
tokenEndpoint: opts.tokenEndpoint,
|
|
165
|
-
|
|
173
|
+
// The inner client reads this bag as `httpClient`; handing it over under
|
|
174
|
+
// the name it is called here meant an operator's configuration applied to
|
|
175
|
+
// the backchannel and token POSTs and was silently dropped on discovery
|
|
176
|
+
// and JWKS — the same accepted-but-ignored split the allowInternal note
|
|
177
|
+
// below records.
|
|
178
|
+
httpClient: opts.httpClientOpts,
|
|
179
|
+
http: opts.http,
|
|
166
180
|
allowHttp: opts.allowHttp === true,
|
|
167
181
|
// Thread the SSRF opt-in through to the inner client's discovery / JWKS /
|
|
168
182
|
// token fetches — an operator with an internal-network IdP needs it, and
|
|
@@ -260,7 +274,10 @@ function create(opts) {
|
|
|
260
274
|
safeUrl.parse(url, {
|
|
261
275
|
allowedProtocols: opts.allowHttp === true ? safeUrl.ALLOW_HTTP_ALL : safeUrl.ALLOW_HTTP_TLS,
|
|
262
276
|
});
|
|
263
|
-
|
|
277
|
+
// A supplied client is wrapped in the pin — see the note in oauth.create.
|
|
278
|
+
var hc = httpOpts.client
|
|
279
|
+
? httpClient().pinnedClient(httpOpts.client, httpOpts.allowedHosts)
|
|
280
|
+
: httpClient();
|
|
264
281
|
var basic = _basicAuthHeader();
|
|
265
282
|
var hdrs = Object.assign({
|
|
266
283
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
@@ -280,6 +297,18 @@ function create(opts) {
|
|
|
280
297
|
responseMode: "always-resolve",
|
|
281
298
|
};
|
|
282
299
|
Object.assign(req, opts.httpClientOpts || {});
|
|
300
|
+
// Applied after the options bag so a stray allowedHosts there cannot widen
|
|
301
|
+
// the pin the operator named explicitly.
|
|
302
|
+
if (httpOpts.allowedHosts) req.allowedHosts = httpOpts.allowedHosts.slice();
|
|
303
|
+
// Re-checked HERE, not only on the endpoint above: the options bag is
|
|
304
|
+
// merged in after that check and can carry a `url` of its own, so the
|
|
305
|
+
// address actually dialed need not be the one that was validated. These
|
|
306
|
+
// are the requests holding the Basic credentials or the client assertion,
|
|
307
|
+
// and a host pin does not stand in for the scheme — it deliberately admits
|
|
308
|
+
// both. This is the last point before the wire.
|
|
309
|
+
safeUrl.parse(req.url, {
|
|
310
|
+
allowedProtocols: opts.allowHttp === true ? safeUrl.ALLOW_HTTP_ALL : safeUrl.ALLOW_HTTP_TLS,
|
|
311
|
+
});
|
|
283
312
|
if (opts.allowHttp === true) req.allowedProtocols = safeUrl.ALLOW_HTTP_ALL;
|
|
284
313
|
// Thread the operator's explicit SSRF waiver onto the backchannel-auth
|
|
285
314
|
// and token POSTs too — create() already hands allowInternal to the
|
|
@@ -104,6 +104,32 @@
|
|
|
104
104
|
* through `b.httpClient` which enforces the framework's PQC-locked
|
|
105
105
|
* posture by default (operators with mixed-protocol setups pass
|
|
106
106
|
* `allowedProtocols`).
|
|
107
|
+
*
|
|
108
|
+
* `http` names the client those fetches are made THROUGH, and the
|
|
109
|
+
* hosts they may reach:
|
|
110
|
+
*
|
|
111
|
+
* var oauth = b.auth.oauth.create({
|
|
112
|
+
* issuer, clientId, clientSecret,
|
|
113
|
+
* http: {
|
|
114
|
+
* client: myHttpClient, // any { request(opts) }
|
|
115
|
+
* allowedHosts: ["api-m.paypal.com"], // pin every dial
|
|
116
|
+
* },
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* Both default to the framework's behaviour. `client` lets a caller
|
|
120
|
+
* that already routes its outbound calls through one circuit breaker
|
|
121
|
+
* and one retry policy keep the token exchange inside them, rather
|
|
122
|
+
* than leaving the endpoint whose failure should open the circuit as
|
|
123
|
+
* the one endpoint the circuit cannot see. `allowedHosts` is passed
|
|
124
|
+
* to `b.httpClient.request`, which enforces it; it covers discovery
|
|
125
|
+
* and JWKS as well as the token endpoint, since a mangled or
|
|
126
|
+
* attacker-influenced issuer is read at discovery — pinning only the
|
|
127
|
+
* token dial would leave the egress open where it starts.
|
|
128
|
+
*
|
|
129
|
+
* Note `httpClient` (options merged INTO each request) and
|
|
130
|
+
* `http.client` (the client each request is made through) are
|
|
131
|
+
* different options; the host pin is applied after the options bag,
|
|
132
|
+
* so it cannot be widened from there.
|
|
107
133
|
*/
|
|
108
134
|
|
|
109
135
|
var nodeCrypto = require("node:crypto");
|
|
@@ -1116,6 +1142,21 @@ function create(opts) {
|
|
|
1116
1142
|
var allowHttp = !!opts.allowHttp; // localhost dev opt-in (scheme)
|
|
1117
1143
|
var allowInternal = opts.allowInternal != null ? opts.allowInternal : null; // localhost dev opt-in (SSRF gate)
|
|
1118
1144
|
var httpClientOpts = opts.httpClient || {};
|
|
1145
|
+
// The client this module dials with, and the host pin every dial carries.
|
|
1146
|
+
// `opts.httpClient` is options merged INTO each request; `opts.http.client`
|
|
1147
|
+
// is the client the request is made through. A consumer that instruments its
|
|
1148
|
+
// outbound calls — one circuit breaker, one retry policy, one recorded
|
|
1149
|
+
// transcript — needs the token exchange inside that, or the endpoint whose
|
|
1150
|
+
// failure should open the circuit is the one endpoint the circuit cannot see.
|
|
1151
|
+
// A supplied client is wrapped in the host pin rather than handed it as a
|
|
1152
|
+
// request field: its contract is a `request` method, so it need not know
|
|
1153
|
+
// what allowedHosts means, and an advisory pin a client ignores is no pin.
|
|
1154
|
+
// b.httpClient enforces the field itself, so the unwrapped default is
|
|
1155
|
+
// already covered.
|
|
1156
|
+
var httpOpts = validateOpts.outboundHttpOpts(opts.http, "oauth.create", OAuthError, "auth-oauth");
|
|
1157
|
+
var httpDialer = httpOpts.client
|
|
1158
|
+
? httpClient.pinnedClient(httpOpts.client, httpOpts.allowedHosts)
|
|
1159
|
+
: httpClient;
|
|
1119
1160
|
var responseMode = opts.responseMode || null;
|
|
1120
1161
|
// v0.9.5 — client-level opt-out for the kid-less JWKS-of-one
|
|
1121
1162
|
// refusal added in v0.9.4. Surfaced at the create() level (not
|
|
@@ -1218,7 +1259,7 @@ function create(opts) {
|
|
|
1218
1259
|
|
|
1219
1260
|
async function _fetchJson(url, fetchOpts) {
|
|
1220
1261
|
fetchOpts = fetchOpts || {};
|
|
1221
|
-
var hc =
|
|
1262
|
+
var hc = httpDialer;
|
|
1222
1263
|
var req = Object.assign({
|
|
1223
1264
|
url: url,
|
|
1224
1265
|
method: "GET",
|
|
@@ -1789,7 +1830,7 @@ function create(opts) {
|
|
|
1789
1830
|
// Revocation endpoint (RFC 7009) — authenticate with client_secret_post (the
|
|
1790
1831
|
// credentials stay in the body, universally accepted). tokenEndpointAuthMethod
|
|
1791
1832
|
// is scoped to the token endpoint; this endpoint may advertise a different one.
|
|
1792
|
-
var hc =
|
|
1833
|
+
var hc = httpDialer;
|
|
1793
1834
|
var req = {
|
|
1794
1835
|
url: endpoint,
|
|
1795
1836
|
method: "POST",
|
|
@@ -1816,6 +1857,22 @@ function create(opts) {
|
|
|
1816
1857
|
var ownHeaders = req.headers || {};
|
|
1817
1858
|
Object.assign(req, httpClientOpts); // may set req.headers from the operator config (always an object)
|
|
1818
1859
|
req.headers = Object.assign({}, req.headers, ownHeaders);
|
|
1860
|
+
// The host pin is applied AFTER the options bag, so a stray allowedHosts
|
|
1861
|
+
// in the loose `httpClient` config cannot widen the pin the operator named
|
|
1862
|
+
// explicitly. Every request this module builds passes through here, which
|
|
1863
|
+
// is what makes the pin cover discovery and JWKS as well as the token
|
|
1864
|
+
// endpoint — a mangled issuer is read at discovery, so pinning only the
|
|
1865
|
+
// token dial would leave the SSRF egress open where it actually starts.
|
|
1866
|
+
if (httpOpts.allowedHosts) req.allowedHosts = httpOpts.allowedHosts.slice();
|
|
1867
|
+
// The scheme is this module's requirement, not a side effect of dialing
|
|
1868
|
+
// through b.httpClient. That client refuses a non-TLS destination itself,
|
|
1869
|
+
// so while it was the only dialer the check came for free; a supplied
|
|
1870
|
+
// client carries no such promise, and these requests are the ones holding
|
|
1871
|
+
// the client secret, the authorization code and the access token. An
|
|
1872
|
+
// endpoint reaches here from static config OR from a discovery document,
|
|
1873
|
+
// so it is checked here rather than at either source — this is the last
|
|
1874
|
+
// point before the wire, and after the options bag has had its say.
|
|
1875
|
+
_validateUrl(req.url, allowHttp, "outbound endpoint");
|
|
1819
1876
|
}
|
|
1820
1877
|
|
|
1821
1878
|
// RFC 7231 §7.1.3 Retry-After → milliseconds. Accepts delta-seconds or an
|
|
@@ -1847,7 +1904,7 @@ function create(opts) {
|
|
|
1847
1904
|
}
|
|
1848
1905
|
|
|
1849
1906
|
async function _postForm(endpoint, body, extraHeaders, applyTokenClientAuth) {
|
|
1850
|
-
var hc =
|
|
1907
|
+
var hc = httpDialer;
|
|
1851
1908
|
// tokenEndpointAuthMethod (client_secret_basic) is the TOKEN endpoint's client
|
|
1852
1909
|
// authentication — apply it only to token-endpoint requests. Other endpoints
|
|
1853
1910
|
// (introspection / revocation / PAR / device-authorization) may advertise a
|
|
@@ -2783,7 +2840,7 @@ function create(opts) {
|
|
|
2783
2840
|
throw new OAuthError("auth-oauth/no-registration-endpoint",
|
|
2784
2841
|
"registerClient: AS does not advertise registration_endpoint");
|
|
2785
2842
|
}
|
|
2786
|
-
var hc =
|
|
2843
|
+
var hc = httpDialer;
|
|
2787
2844
|
var headers = {
|
|
2788
2845
|
"Content-Type": "application/json",
|
|
2789
2846
|
"Accept": "application/json",
|
|
@@ -2923,10 +2980,10 @@ function create(opts) {
|
|
|
2923
2980
|
if (allowHttp) req.allowedProtocols = safeUrl.ALLOW_HTTP_ALL;
|
|
2924
2981
|
if (allowInternal !== null) req.allowInternal = allowInternal;
|
|
2925
2982
|
_mergeHttpClientOpts(req);
|
|
2926
|
-
var res = await
|
|
2983
|
+
var res = await httpDialer.request(req);
|
|
2927
2984
|
if (method === "DELETE") {
|
|
2928
|
-
if (res.statusCode ===
|
|
2929
|
-
if (res.statusCode ===
|
|
2985
|
+
if (res.statusCode === C.HTTP.STATUS.NO_CONTENT || res.statusCode === C.HTTP.STATUS.OK) return null;
|
|
2986
|
+
if (res.statusCode === C.HTTP.STATUS.NOT_FOUND) {
|
|
2930
2987
|
throw new OAuthError("auth-oauth/dcr-not-found",
|
|
2931
2988
|
"deleteClient: 404 — registrationClientUri does not resolve to a client");
|
|
2932
2989
|
}
|
|
@@ -3052,7 +3109,7 @@ function create(opts) {
|
|
|
3052
3109
|
// its own request rather than routing through _postForm because it must
|
|
3053
3110
|
// read the pending/slow_down 400 bodies, so it composes the shared helper).
|
|
3054
3111
|
var authHeaders = _clientBasicAuthHeaders(body);
|
|
3055
|
-
var hc =
|
|
3112
|
+
var hc = httpDialer;
|
|
3056
3113
|
var req = {
|
|
3057
3114
|
url: endpoint,
|
|
3058
3115
|
method: "POST",
|
|
@@ -448,7 +448,7 @@ function policy(opts) {
|
|
|
448
448
|
return _ok({ breachCheckSkipped: true,
|
|
449
449
|
breachCheckSkipReason: (e && e.message) || String(e) });
|
|
450
450
|
}
|
|
451
|
-
if (resp.statusCode !==
|
|
451
|
+
if (resp.statusCode !== C.HTTP.STATUS.OK || !resp.body) {
|
|
452
452
|
if (p.failClosed) {
|
|
453
453
|
return _fail("breach-check-failed",
|
|
454
454
|
"HIBP returned status " + resp.statusCode + " with no body");
|
|
@@ -57,6 +57,7 @@ var bCrypto = require("../crypto");
|
|
|
57
57
|
var C = require("../constants");
|
|
58
58
|
var { generateToken, timingSafeEqual } = bCrypto;
|
|
59
59
|
var { AuthError } = require("../framework-error");
|
|
60
|
+
var safeUrl = require("../safe-url");
|
|
60
61
|
|
|
61
62
|
var xmlC14n = lazyRequire(function () { return require("../xml-c14n"); });
|
|
62
63
|
var httpClient = lazyRequire(function () { return require("../http-client"); });
|
|
@@ -2138,6 +2139,7 @@ function _sigAlgUrn(alg) {
|
|
|
2138
2139
|
* baseUrl: string,
|
|
2139
2140
|
* entityId: string,
|
|
2140
2141
|
* trustCertPem?: string, // PEM of the federation operator's signing cert
|
|
2142
|
+
* http?: { client?: object, allowedHosts?: string[] }, // the client the fetch is made through, and its host pin
|
|
2141
2143
|
* }
|
|
2142
2144
|
*
|
|
2143
2145
|
* @example
|
|
@@ -2151,14 +2153,30 @@ async function fetchMdq(opts) {
|
|
|
2151
2153
|
validateOpts.requireObject(opts, "auth.saml.fetchMdq", AuthError);
|
|
2152
2154
|
validateOpts.requireNonEmptyString(opts.baseUrl, "baseUrl", AuthError, "auth-saml/no-mdq-base");
|
|
2153
2155
|
validateOpts.requireNonEmptyString(opts.entityId, "entityId", AuthError, "auth-saml/no-mdq-entity");
|
|
2156
|
+
var httpOpts = validateOpts.outboundHttpOpts(opts.http, "auth.saml.fetchMdq",
|
|
2157
|
+
AuthError, "auth-saml");
|
|
2154
2158
|
var hash = nodeCrypto.createHash("sha1").update(opts.entityId, "utf8").digest("hex");
|
|
2155
2159
|
var url = opts.baseUrl.replace(/\/$/, "") + "/entities/%7Bsha1%7D" + hash;
|
|
2156
|
-
|
|
2157
|
-
|
|
2160
|
+
// The scheme is checked HERE, not left to whichever client dials. b.httpClient
|
|
2161
|
+
// refuses a non-TLS destination itself, so routing through it was enough while
|
|
2162
|
+
// it was the only client; an injected one carries no such promise, and
|
|
2163
|
+
// trustCertPem is optional — an http:// metadata fetch with no signature to
|
|
2164
|
+
// check lets anyone on the path substitute the federation's signing keys.
|
|
2165
|
+
safeUrl.parse(url, {
|
|
2166
|
+
allowedProtocols: safeUrl.ALLOW_HTTP_TLS,
|
|
2167
|
+
errorClass: AuthError,
|
|
2168
|
+
});
|
|
2169
|
+
// A supplied client is wrapped in the pin — see the note in oauth.create.
|
|
2170
|
+
var hc = httpOpts.client
|
|
2171
|
+
? httpClient().pinnedClient(httpOpts.client, httpOpts.allowedHosts)
|
|
2172
|
+
: httpClient();
|
|
2173
|
+
var req = {
|
|
2158
2174
|
url: url,
|
|
2159
2175
|
method: "GET",
|
|
2160
2176
|
headers: { Accept: "application/samlmetadata+xml" },
|
|
2161
|
-
}
|
|
2177
|
+
};
|
|
2178
|
+
if (httpOpts.allowedHosts) req.allowedHosts = httpOpts.allowedHosts.slice();
|
|
2179
|
+
var res = await hc.request(req);
|
|
2162
2180
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
2163
2181
|
throw new AuthError("auth-saml/mdq-fetch-failed",
|
|
2164
2182
|
"fetchMdq " + url + " returned " + res.statusCode);
|
|
@@ -243,7 +243,7 @@ function create(opts) {
|
|
|
243
243
|
function _runBotGuardCheck(req) {
|
|
244
244
|
return new Promise(function (resolve) {
|
|
245
245
|
var capturedRes = {
|
|
246
|
-
statusCode:
|
|
246
|
+
statusCode: C.HTTP.STATUS.OK, // HTTP 200 status code, not bytes
|
|
247
247
|
writableEnded: false,
|
|
248
248
|
writeHead: function (status) { capturedRes.statusCode = status; },
|
|
249
249
|
end: function () { capturedRes.writableEnded = true; },
|
|
@@ -400,7 +400,7 @@ function create(opts) {
|
|
|
400
400
|
function _writeChallengeRequired(res) {
|
|
401
401
|
if (!res || res.writableEnded) return;
|
|
402
402
|
if (typeof res.writeHead === "function") {
|
|
403
|
-
res.writeHead(
|
|
403
|
+
res.writeHead(C.HTTP.STATUS.UNAUTHORIZED, {
|
|
404
404
|
"Content-Type": "text/plain",
|
|
405
405
|
"WWW-Authenticate": 'Bearer error="bot_challenge_required"',
|
|
406
406
|
});
|
|
@@ -413,7 +413,7 @@ function create(opts) {
|
|
|
413
413
|
function _writeLocked(res) {
|
|
414
414
|
if (!res || res.writableEnded) return;
|
|
415
415
|
if (typeof res.writeHead === "function") {
|
|
416
|
-
res.writeHead(
|
|
416
|
+
res.writeHead(C.HTTP.STATUS.LOCKED, { "Content-Type": "text/plain" });
|
|
417
417
|
} else if (typeof res.statusCode !== "undefined") {
|
|
418
418
|
res.statusCode = 423;
|
|
419
419
|
}
|
|
@@ -2416,7 +2416,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2416
2416
|
// b.objectStore surfaces NOT_FOUND via the framework's
|
|
2417
2417
|
// err.code === "NOT_FOUND" convention — translate to the
|
|
2418
2418
|
// backup adapter contract's no-key error.
|
|
2419
|
-
if (e && (e.code === "NOT_FOUND" || e.statusCode ===
|
|
2419
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === C.HTTP.STATUS.NOT_FOUND || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2420
2420
|
throw new BackupError("backup/no-key",
|
|
2421
2421
|
"objectStoreAdapter: key not found: " + JSON.stringify(key));
|
|
2422
2422
|
}
|
|
@@ -2473,7 +2473,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2473
2473
|
} catch (e) {
|
|
2474
2474
|
// drop-silent on NOT_FOUND — adapter contract is idempotent
|
|
2475
2475
|
// delete (fsAdapter same shape).
|
|
2476
|
-
if (e && (e.code === "NOT_FOUND" || e.statusCode ===
|
|
2476
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === C.HTTP.STATUS.NOT_FOUND || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2477
2477
|
return;
|
|
2478
2478
|
}
|
|
2479
2479
|
throw e;
|
|
@@ -2484,7 +2484,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2484
2484
|
await client.head(_scopedKey(key));
|
|
2485
2485
|
return true;
|
|
2486
2486
|
} catch (e) {
|
|
2487
|
-
if (e && (e.code === "NOT_FOUND" || e.statusCode ===
|
|
2487
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === C.HTTP.STATUS.NOT_FOUND || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2488
2488
|
return false;
|
|
2489
2489
|
}
|
|
2490
2490
|
throw e;
|
|
@@ -2501,7 +2501,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2501
2501
|
var buf = Buffer.isBuffer(body) ? body : Buffer.from(body);
|
|
2502
2502
|
return buf.slice(0, length);
|
|
2503
2503
|
} catch (e) {
|
|
2504
|
-
if (e && (e.code === "NOT_FOUND" || e.statusCode ===
|
|
2504
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === C.HTTP.STATUS.NOT_FOUND || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2505
2505
|
throw new BackupError("backup/no-key",
|
|
2506
2506
|
"objectStoreAdapter.readPartial: key not found: " + JSON.stringify(key));
|
|
2507
2507
|
}
|
|
@@ -2514,7 +2514,7 @@ bundleAdapterStorage.objectStoreAdapter = function (client, osOpts) {
|
|
|
2514
2514
|
if (!meta || typeof meta.size !== "number") return null;
|
|
2515
2515
|
return { size: meta.size, mtimeMs: meta.lastModified || null };
|
|
2516
2516
|
} catch (e) {
|
|
2517
|
-
if (e && (e.code === "NOT_FOUND" || e.statusCode ===
|
|
2517
|
+
if (e && (e.code === "NOT_FOUND" || e.statusCode === C.HTTP.STATUS.NOT_FOUND || /NOT_FOUND|not found/i.test(e.message || ""))) {
|
|
2518
2518
|
return null;
|
|
2519
2519
|
}
|
|
2520
2520
|
throw e;
|