@blamejs/core 0.14.20 → 0.14.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 +2 -0
- package/lib/auth/oid4vci.js +124 -5
- package/lib/auth/oid4vp.js +14 -4
- package/lib/break-glass.js +1 -2
- package/lib/config.js +28 -31
- package/lib/dora.js +8 -5
- package/lib/dsr.js +2 -2
- package/lib/flag-evaluation-context.js +7 -0
- package/lib/guard-html-wcag-aria.js +4 -2
- package/lib/guard-html-wcag-forms.js +4 -2
- package/lib/guard-html-wcag-tables.js +4 -2
- package/lib/guard-html-wcag-tagwalk.js +20 -0
- package/lib/guard-html-wcag.js +1 -1
- package/lib/honeytoken.js +27 -20
- package/lib/mail-deploy.js +1 -1
- package/lib/mail-send-deliver.js +13 -4
- package/lib/middleware/api-encrypt.js +140 -13
- package/lib/middleware/asyncapi-serve.js +3 -0
- package/lib/middleware/csp-report.js +13 -9
- package/lib/middleware/openapi-serve.js +3 -0
- package/lib/middleware/scim-server.js +297 -19
- package/lib/middleware/security-txt.js +1 -2
- package/lib/middleware/trace-log-correlation.js +1 -2
- package/lib/network-smtp-policy.js +4 -4
- package/lib/object-store/sigv4-bucket-ops.js +11 -2
- package/lib/observability-tracer.js +1 -1
- package/lib/problem-details.js +56 -11
- package/lib/pubsub-cluster.js +16 -3
- package/lib/queue-sqs.js +20 -2
- package/lib/redis-client.js +32 -4
- package/lib/safe-redirect.js +16 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -57,7 +57,7 @@ var BEARER_RE = /^Bearer\s+(.+)$/i;
|
|
|
57
57
|
* users: ScimResourceImpl,
|
|
58
58
|
* groups?: ScimResourceImpl,
|
|
59
59
|
* bearer?: (token) => Promise<actor>,
|
|
60
|
-
* maxPageSize?: number,
|
|
60
|
+
* maxPageSize?: number, // default: 200 (config-time positive int)
|
|
61
61
|
* bulk?: {
|
|
62
62
|
* maxOperations?: number, // default: 1000 (config-time positive int)
|
|
63
63
|
* maxPayloadSize?: number, // default: 1 MiB (config-time positive int, bytes)
|
|
@@ -80,6 +80,12 @@ function create(opts) {
|
|
|
80
80
|
if (opts.groups) _validateResourceImpl(opts.groups, "groups");
|
|
81
81
|
|
|
82
82
|
var basePath = opts.basePath || "/scim/v2";
|
|
83
|
+
// Config-time / entry-point tier: a bad page-size cap THROWS so the
|
|
84
|
+
// operator catches the typo at boot rather than at request time, where
|
|
85
|
+
// a non-number would propagate NaN into impl.list({ count }) and
|
|
86
|
+
// ServiceProviderConfig.filter.maxResults.
|
|
87
|
+
validateOpts.optionalPositiveInt(opts.maxPageSize, "middleware.scimServer: opts.maxPageSize",
|
|
88
|
+
ScimServerError, "middleware/scim-server/bad-max-page-size");
|
|
83
89
|
var maxPageSize = opts.maxPageSize || 200; // page-size count, not bytes
|
|
84
90
|
var bearer = opts.bearer || null;
|
|
85
91
|
var bulkCfg = _resolveBulkConfig(opts.bulk);
|
|
@@ -297,11 +303,15 @@ async function _dispatch(req, res, basePath, bearer, opts, maxPageSize, bulkCfg)
|
|
|
297
303
|
}
|
|
298
304
|
|
|
299
305
|
// RFC 7644 §3.7 — /Bulk POST. Parses a BulkRequest, enforces the
|
|
300
|
-
// config-time maxOperations / maxPayloadSize caps,
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
306
|
+
// config-time maxOperations / maxPayloadSize caps, plans a
|
|
307
|
+
// dependency-ordered execution so bulkId cross-references (§3.7.2) —
|
|
308
|
+
// including forward references — resolve to real ids before dispatch,
|
|
309
|
+
// fails operations with undeclared / circular / failed-dependency
|
|
310
|
+
// references per-op, optionally short-circuits at failOnErrors,
|
|
311
|
+
// dispatches each surviving operation through the same per-resource
|
|
312
|
+
// create/update/delete logic the singleton endpoints use, and returns a
|
|
313
|
+
// BulkResponse carrying one result object per operation in the ORIGINAL
|
|
314
|
+
// request order.
|
|
305
315
|
async function _handleBulk(req, res, opts, bulkCfg, ctx) {
|
|
306
316
|
var body;
|
|
307
317
|
try {
|
|
@@ -336,26 +346,265 @@ async function _handleBulk(req, res, opts, bulkCfg, ctx) {
|
|
|
336
346
|
|
|
337
347
|
var failOnErrors = _parseFailOnErrors(body.failOnErrors);
|
|
338
348
|
var bulkIdMap = Object.create(null); // client bulkId -> assigned resource id
|
|
339
|
-
var
|
|
340
|
-
|
|
349
|
+
var ops = body.Operations;
|
|
350
|
+
|
|
351
|
+
// RFC 7644 §3.7.2 — a bulkId reference may point at a resource a LATER
|
|
352
|
+
// operation creates (a forward reference), so processing strictly in
|
|
353
|
+
// request order leaves the token unresolved. Pre-scan to (a) collect
|
|
354
|
+
// every declared bulkId and (b) build each operation's dependency set
|
|
355
|
+
// by walking its data for "bulkId:<id>" cross-references, then execute
|
|
356
|
+
// in dependency order. The response array is still emitted in the
|
|
357
|
+
// ORIGINAL request order (results indexed by request position).
|
|
358
|
+
var plan = _planBulkOperations(ops);
|
|
359
|
+
|
|
360
|
+
var results = new Array(ops.length); // request-order result entries
|
|
361
|
+
var executed = new Array(ops.length); // index -> { isError } once run
|
|
362
|
+
var errorCount = 0;
|
|
363
|
+
var stopped = false;
|
|
364
|
+
|
|
365
|
+
for (var s = 0; s < plan.order.length && !stopped; s++) {
|
|
366
|
+
var idx = plan.order[s];
|
|
367
|
+
var planned = plan.ops[idx];
|
|
368
|
+
var outcome;
|
|
369
|
+
|
|
370
|
+
if (planned.staticError) {
|
|
371
|
+
// Undeclared reference or a reference that lands in a dependency
|
|
372
|
+
// cycle — fail the op without ever dispatching to the adapter.
|
|
373
|
+
outcome = _bulkErr(ops[idx], planned.staticError.status,
|
|
374
|
+
planned.staticError.scimType, planned.staticError.detail);
|
|
375
|
+
} else if (_anyDependencyFailed(planned.refs, plan, executed, bulkIdMap)) {
|
|
376
|
+
// RFC 7644 §3.7.2 — a reference to an operation that FAILED cannot
|
|
377
|
+
// resolve to a real id; fail the dependent op with invalidValue
|
|
378
|
+
// rather than letting a literal "bulkId:<id>" token reach the
|
|
379
|
+
// adapter as if it were a real resource identifier.
|
|
380
|
+
outcome = _bulkErr(ops[idx], "400", "invalidValue",
|
|
381
|
+
"Operations[" + idx + "] references a bulkId whose creating operation failed");
|
|
382
|
+
} else {
|
|
383
|
+
outcome = await _runBulkOperation(ops[idx], idx, opts, ctx, bulkIdMap);
|
|
384
|
+
}
|
|
341
385
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (result.isError) {
|
|
386
|
+
results[idx] = outcome.entry;
|
|
387
|
+
executed[idx] = { isError: outcome.isError };
|
|
388
|
+
if (outcome.isError) {
|
|
346
389
|
errorCount++;
|
|
347
390
|
// RFC 7644 §3.7 — once the error count reaches failOnErrors the
|
|
348
|
-
// service stops processing and returns the results so far.
|
|
349
|
-
|
|
391
|
+
// service stops processing and returns the results so far. Results
|
|
392
|
+
// already produced keep their request-order slots; unreached
|
|
393
|
+
// operations are omitted from the response.
|
|
394
|
+
if (failOnErrors !== null && errorCount >= failOnErrors) stopped = true;
|
|
350
395
|
}
|
|
351
396
|
}
|
|
352
397
|
|
|
398
|
+
// Compact to the operations actually reached, preserving request order.
|
|
399
|
+
var emitted = [];
|
|
400
|
+
for (var e = 0; e < results.length; e++) {
|
|
401
|
+
if (results[e] !== undefined) emitted.push(results[e]);
|
|
402
|
+
}
|
|
403
|
+
|
|
353
404
|
_writeJson(res, H.OK, {
|
|
354
405
|
schemas: [SCIM_MESSAGE_BULK_RESPONSE],
|
|
355
|
-
Operations:
|
|
406
|
+
Operations: emitted,
|
|
356
407
|
});
|
|
357
408
|
}
|
|
358
409
|
|
|
410
|
+
// RFC 7644 §3.7.2 — build the dependency-ordered execution plan for a
|
|
411
|
+
// bulk job. Returns { ops, order } where ops[i] = { refs, staticError? }
|
|
412
|
+
// (refs = the set of declared bulkIds operation i depends on) and order
|
|
413
|
+
// is the request indices in a dependency-respecting (topological)
|
|
414
|
+
// sequence. Operations referencing an UNDECLARED bulkId, or caught in a
|
|
415
|
+
// dependency CYCLE, carry a staticError so the executor fails them
|
|
416
|
+
// without dispatching to the adapter. A POST that declares a bulkId is
|
|
417
|
+
// the only operation kind that can satisfy a reference.
|
|
418
|
+
function _planBulkOperations(ops) {
|
|
419
|
+
var declared = Object.create(null); // bulkId -> declaring index
|
|
420
|
+
var planned = new Array(ops.length);
|
|
421
|
+
|
|
422
|
+
for (var i = 0; i < ops.length; i++) {
|
|
423
|
+
var op = ops[i];
|
|
424
|
+
var bulkId = op && typeof op.bulkId === "string" ? op.bulkId : null;
|
|
425
|
+
var method = op && typeof op.method === "string" ? op.method.toUpperCase() : null;
|
|
426
|
+
// Only a POST (resource creation) assigns a server id to a bulkId.
|
|
427
|
+
if (bulkId && method === "POST" && declared[bulkId] === undefined) {
|
|
428
|
+
declared[bulkId] = i;
|
|
429
|
+
}
|
|
430
|
+
planned[i] = { refs: [], staticError: null, ownBulkId: method === "POST" ? bulkId : null };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
for (var j = 0; j < ops.length; j++) {
|
|
434
|
+
// RFC 7644 §3.7.2 — a reference can appear in the operation DATA
|
|
435
|
+
// ("value": "bulkId:u1") or as the resource id in the operation
|
|
436
|
+
// PATH ("PATCH /Groups/bulkId:g1" targeting a group another
|
|
437
|
+
// operation in this request creates). Both surfaces feed the same
|
|
438
|
+
// dependency set so path-referencing operations order and fail
|
|
439
|
+
// exactly like data-referencing ones.
|
|
440
|
+
var refs = _collectBulkIdRefs(ops[j] && ops[j].data).concat(_pathBulkIdRefs(ops[j]));
|
|
441
|
+
var depSet = [];
|
|
442
|
+
for (var r = 0; r < refs.length; r++) {
|
|
443
|
+
var refId = refs[r];
|
|
444
|
+
var decl = declared[refId];
|
|
445
|
+
if (decl === undefined) {
|
|
446
|
+
// RFC 7644 §3.7.2 — a reference to a bulkId no operation declares
|
|
447
|
+
// can never resolve; fail this op with invalidValue.
|
|
448
|
+
planned[j].staticError = {
|
|
449
|
+
status: "400",
|
|
450
|
+
scimType: "invalidValue",
|
|
451
|
+
detail: "Operations[" + j + "] references undeclared bulkId '" + refId + "'",
|
|
452
|
+
};
|
|
453
|
+
depSet = [];
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
if (decl !== j && depSet.indexOf(decl) === -1) depSet.push(decl);
|
|
457
|
+
}
|
|
458
|
+
planned[j].refs = depSet;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
var order = _topoOrderBulk(planned);
|
|
462
|
+
return { ops: planned, order: order };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Walk operation data for "bulkId:<id>" cross-references, returning the
|
|
466
|
+
// list of referenced bulkIds (the "<id>" portion). Bounded: the whole
|
|
467
|
+
// payload was capped at maxPayloadSize before parse.
|
|
468
|
+
function _collectBulkIdRefs(value) {
|
|
469
|
+
var out = [];
|
|
470
|
+
_walkBulkIdRefs(value, out);
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// RFC 7644 §3.7.2 — bulkId references in an operation's PATH segments
|
|
475
|
+
// (e.g. "PATCH /Groups/bulkId:g1"). Returns the referenced bulkIds.
|
|
476
|
+
function _pathBulkIdRefs(op) {
|
|
477
|
+
if (!op || typeof op.path !== "string") return [];
|
|
478
|
+
var out = [];
|
|
479
|
+
var segs = op.path.split("/");
|
|
480
|
+
for (var i = 0; i < segs.length; i++) {
|
|
481
|
+
var ref = BULK_ID_REF_RE.exec(segs[i]);
|
|
482
|
+
if (ref) out.push(ref[1]);
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Substitute "bulkId:<id>" PATH segments with the server-assigned id,
|
|
488
|
+
// exactly like data references resolve (RFC 7644 §3.7.2). Throws on an
|
|
489
|
+
// unresolved token — surfaced as a per-op invalidValue by the caller —
|
|
490
|
+
// so a literal token never reaches the path parser or the adapter.
|
|
491
|
+
function _resolvePathBulkIdRefs(path, bulkIdMap) {
|
|
492
|
+
var segs = path.split("/");
|
|
493
|
+
for (var i = 0; i < segs.length; i++) {
|
|
494
|
+
var ref = BULK_ID_REF_RE.exec(segs[i]);
|
|
495
|
+
if (!ref) continue;
|
|
496
|
+
var resolved = bulkIdMap[ref[1]];
|
|
497
|
+
if (resolved === undefined) {
|
|
498
|
+
throw new ScimServerError("middleware/scim-server/unresolved-bulkid",
|
|
499
|
+
"references unresolved bulkId '" + ref[1] + "' in path");
|
|
500
|
+
}
|
|
501
|
+
segs[i] = resolved;
|
|
502
|
+
}
|
|
503
|
+
return segs.join("/");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function _walkBulkIdRefs(value, out) {
|
|
507
|
+
if (typeof value === "string") {
|
|
508
|
+
var ref = BULK_ID_REF_RE.exec(value);
|
|
509
|
+
if (ref) out.push(ref[1]);
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (Array.isArray(value)) {
|
|
513
|
+
for (var i = 0; i < value.length; i++) _walkBulkIdRefs(value[i], out);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (value && typeof value === "object") {
|
|
517
|
+
var keys = Object.keys(value);
|
|
518
|
+
for (var k = 0; k < keys.length; k++) {
|
|
519
|
+
if (keys[k] === "__proto__" || keys[k] === "constructor" || keys[k] === "prototype") continue;
|
|
520
|
+
_walkBulkIdRefs(value[keys[k]], out);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Dependency-respecting order over the planned operations. Operations
|
|
526
|
+
// already carrying a staticError (undeclared reference) are treated as
|
|
527
|
+
// dependency-free roots so they surface their own error in request
|
|
528
|
+
// order. Any operation that cannot be ordered because it participates in
|
|
529
|
+
// a CYCLE is marked with a 409 staticError per RFC 7644 §3.7.1 — "The
|
|
530
|
+
// service provider MUST try to resolve circular cross-references ... but
|
|
531
|
+
// MAY ... return HTTP status code 409 (Conflict)". The returned order
|
|
532
|
+
// always covers every index exactly once.
|
|
533
|
+
function _topoOrderBulk(planned) {
|
|
534
|
+
var n = planned.length;
|
|
535
|
+
var visited = new Array(n); // 0 = unseen, 1 = on-stack, 2 = done
|
|
536
|
+
for (var v = 0; v < n; v++) visited[v] = 0;
|
|
537
|
+
var order = [];
|
|
538
|
+
var inCycle = Object.create(null);
|
|
539
|
+
|
|
540
|
+
// Iterative depth-first post-order so a dependency is emitted before
|
|
541
|
+
// the operation that needs it. A back-edge (a node still on-stack)
|
|
542
|
+
// marks a cycle; every node on the active stack at that point is part
|
|
543
|
+
// of an unresolvable circular reference.
|
|
544
|
+
for (var start = 0; start < n; start++) {
|
|
545
|
+
if (visited[start] !== 0) continue;
|
|
546
|
+
var stack = [{ node: start, edge: 0 }];
|
|
547
|
+
while (stack.length > 0) {
|
|
548
|
+
var top = stack[stack.length - 1];
|
|
549
|
+
var node = top.node;
|
|
550
|
+
if (top.edge === 0) visited[node] = 1;
|
|
551
|
+
var deps = (planned[node].staticError) ? [] : planned[node].refs;
|
|
552
|
+
if (top.edge < deps.length) {
|
|
553
|
+
var dep = deps[top.edge];
|
|
554
|
+
top.edge++;
|
|
555
|
+
if (visited[dep] === 0) {
|
|
556
|
+
stack.push({ node: dep, edge: 0 });
|
|
557
|
+
} else if (visited[dep] === 1) {
|
|
558
|
+
// Back-edge to a node still on the active stack — every node
|
|
559
|
+
// from that node up to the current top forms the cycle.
|
|
560
|
+
var depPos = -1;
|
|
561
|
+
for (var p = 0; p < stack.length; p++) {
|
|
562
|
+
if (stack[p].node === dep) { depPos = p; break; }
|
|
563
|
+
}
|
|
564
|
+
for (var q = depPos; q >= 0 && q < stack.length; q++) {
|
|
565
|
+
inCycle[stack[q].node] = true;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
} else {
|
|
569
|
+
visited[node] = 2;
|
|
570
|
+
order.push(node);
|
|
571
|
+
stack.pop();
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
for (var c = 0; c < n; c++) {
|
|
577
|
+
if (inCycle[c] && !planned[c].staticError) {
|
|
578
|
+
planned[c].staticError = {
|
|
579
|
+
status: "409",
|
|
580
|
+
scimType: "invalidValue",
|
|
581
|
+
detail: "Operations[" + c + "] is part of an unresolvable circular bulkId reference",
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return order;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// True when any of the bulkIds this operation references belongs to a
|
|
589
|
+
// creating operation that ran and FAILED (so no id was recorded in
|
|
590
|
+
// bulkIdMap). A still-pending dependency cannot occur here because the
|
|
591
|
+
// topological order guarantees dependencies execute first.
|
|
592
|
+
function _anyDependencyFailed(refs, plan, executed, bulkIdMap) {
|
|
593
|
+
for (var i = 0; i < refs.length; i++) {
|
|
594
|
+
var declIdx = refs[i];
|
|
595
|
+
var rec = executed[declIdx];
|
|
596
|
+
if (rec && rec.isError) return true;
|
|
597
|
+
// A successfully-created dependency records its id in bulkIdMap; if
|
|
598
|
+
// it ran without error but produced no id, it cannot satisfy a
|
|
599
|
+
// reference either.
|
|
600
|
+
var declOp = plan.ops[declIdx];
|
|
601
|
+
if (rec && !rec.isError && declOp.ownBulkId && bulkIdMap[declOp.ownBulkId] === undefined) {
|
|
602
|
+
return true;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
|
|
359
608
|
// RFC 7644 §3.7 — failOnErrors is an OPTIONAL integer >= 1. Absent /
|
|
360
609
|
// non-conforming values mean "process every operation" (null).
|
|
361
610
|
function _parseFailOnErrors(value) {
|
|
@@ -380,7 +629,19 @@ async function _runBulkOperation(op, index, opts, ctx, bulkIdMap) {
|
|
|
380
629
|
"Operations[" + index + "].method '" + op.method + "' not in POST/PUT/PATCH/DELETE");
|
|
381
630
|
}
|
|
382
631
|
|
|
383
|
-
|
|
632
|
+
// Resolve path bulkId references BEFORE parsing — the dependency-
|
|
633
|
+
// ordered executor guarantees a referenced creation already ran, so
|
|
634
|
+
// an unresolved token here is an unsatisfiable reference, failed
|
|
635
|
+
// per-op (RFC 7644 §3.7.2) rather than handed to the adapter.
|
|
636
|
+
var path = op.path;
|
|
637
|
+
if (typeof path === "string" && path.indexOf("bulkId:") !== -1) {
|
|
638
|
+
try { path = _resolvePathBulkIdRefs(path, bulkIdMap); }
|
|
639
|
+
catch (refErr) {
|
|
640
|
+
return _bulkErr(op, "400", "invalidValue",
|
|
641
|
+
"Operations[" + index + "] " + (refErr && refErr.message ? refErr.message : "has an unresolved bulkId reference in path"));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
var parsed = _parseBulkPath(path);
|
|
384
645
|
if (!parsed) {
|
|
385
646
|
return _bulkErr(op, "400", "invalidValue",
|
|
386
647
|
"Operations[" + index + "].path '" + String(op.path) + "' is not a valid bulk path");
|
|
@@ -402,8 +663,17 @@ async function _runBulkOperation(op, index, opts, ctx, bulkIdMap) {
|
|
|
402
663
|
var data = op.data;
|
|
403
664
|
if (method === "POST" || method === "PUT" || method === "PATCH") {
|
|
404
665
|
// RFC 7644 §3.7.2 — substitute any bulkId cross-references that
|
|
405
|
-
// earlier operations
|
|
406
|
-
|
|
666
|
+
// earlier operations resolved before handing data to the adapter.
|
|
667
|
+
// The dependency-ordered executor guarantees every referenced
|
|
668
|
+
// operation ran first; an unresolved token here would mean an
|
|
669
|
+
// unsatisfiable reference, which is failed per-op rather than passed
|
|
670
|
+
// through to the adapter as a literal "bulkId:<id>" string.
|
|
671
|
+
try {
|
|
672
|
+
data = _resolveBulkIdRefs(op.data, bulkIdMap);
|
|
673
|
+
} catch (refErr) {
|
|
674
|
+
return _bulkErr(op, "400", "invalidValue",
|
|
675
|
+
"Operations[" + index + "] " + (refErr && refErr.message ? refErr.message : "has an unresolved bulkId reference"));
|
|
676
|
+
}
|
|
407
677
|
}
|
|
408
678
|
|
|
409
679
|
try {
|
|
@@ -462,7 +732,15 @@ function _resolveBulkIdRefs(value, bulkIdMap) {
|
|
|
462
732
|
var ref = BULK_ID_REF_RE.exec(value);
|
|
463
733
|
if (ref) {
|
|
464
734
|
var resolved = bulkIdMap[ref[1]];
|
|
465
|
-
|
|
735
|
+
// A literal "bulkId:<id>" token MUST never reach the adapter as a
|
|
736
|
+
// real id. The dependency-ordered executor resolves every
|
|
737
|
+
// reference before dispatch; an unresolved token here signals an
|
|
738
|
+
// unsatisfiable reference, surfaced as a per-op error by the caller.
|
|
739
|
+
if (resolved === undefined) {
|
|
740
|
+
throw new ScimServerError("middleware/scim-server/unresolved-bulkid",
|
|
741
|
+
"references unresolved bulkId '" + ref[1] + "'");
|
|
742
|
+
}
|
|
743
|
+
return resolved;
|
|
466
744
|
}
|
|
467
745
|
return value;
|
|
468
746
|
}
|
|
@@ -82,7 +82,6 @@ function _isoFuture(s) {
|
|
|
82
82
|
* hiring: string,
|
|
83
83
|
* canonical: string|string[],
|
|
84
84
|
* alsoAtRoot: boolean,
|
|
85
|
-
* audit: boolean,
|
|
86
85
|
* }
|
|
87
86
|
*
|
|
88
87
|
* @example
|
|
@@ -99,7 +98,7 @@ function create(opts) {
|
|
|
99
98
|
validateOpts(opts, [
|
|
100
99
|
"contact", "expires", "encryption", "policy", "ack",
|
|
101
100
|
"preferredLanguages", "hiring", "canonical",
|
|
102
|
-
"alsoAtRoot",
|
|
101
|
+
"alsoAtRoot",
|
|
103
102
|
], "middleware.securityTxt");
|
|
104
103
|
|
|
105
104
|
var contact = _arrayOfStrings(opts.contact, "contact");
|
|
@@ -123,7 +123,6 @@ function _wrapLogger(baseLogger, req, opts) {
|
|
|
123
123
|
* logger: object, // required b.log instance
|
|
124
124
|
* reqField: string, // default "log" → req.log
|
|
125
125
|
* includeBaggage: boolean, // default true
|
|
126
|
-
* audit: boolean,
|
|
127
126
|
* }
|
|
128
127
|
*
|
|
129
128
|
* @example
|
|
@@ -138,7 +137,7 @@ function _wrapLogger(baseLogger, req, opts) {
|
|
|
138
137
|
function create(opts) {
|
|
139
138
|
validateOpts.requireObject(opts, "middleware.traceLogCorrelation", TraceLogError);
|
|
140
139
|
validateOpts(opts, [
|
|
141
|
-
"logger", "reqField", "includeBaggage",
|
|
140
|
+
"logger", "reqField", "includeBaggage",
|
|
142
141
|
], "middleware.traceLogCorrelation");
|
|
143
142
|
|
|
144
143
|
if (!opts.logger || typeof opts.logger !== "object") {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*
|
|
22
22
|
* policy.tlsRpt.recordShape({
|
|
23
23
|
* organization: "example.com",
|
|
24
|
-
*
|
|
24
|
+
* policies: [ ... ],
|
|
25
25
|
* ...
|
|
26
26
|
* }) → { ... RFC 8460 TLS-RPT JSON shape ... }
|
|
27
27
|
*
|
|
@@ -519,8 +519,8 @@ function daneVerifyChain(certChain, tlsaRecords, opts) {
|
|
|
519
519
|
function tlsRptRecordShape(opts) {
|
|
520
520
|
opts = opts || {};
|
|
521
521
|
validateOpts(opts, [
|
|
522
|
-
"organization", "
|
|
523
|
-
"datestart", "dateend", "policies",
|
|
522
|
+
"organization", "contact",
|
|
523
|
+
"datestart", "dateend", "policies", "reportId",
|
|
524
524
|
], "tlsRpt.recordShape");
|
|
525
525
|
|
|
526
526
|
if (typeof opts.organization !== "string") {
|
|
@@ -637,7 +637,7 @@ async function tlsRptSubmit(report, opts) {
|
|
|
637
637
|
"tlsRpt.submit: report must be an object");
|
|
638
638
|
}
|
|
639
639
|
opts = opts || {};
|
|
640
|
-
validateOpts(opts, ["rua", "httpClient", "timeoutMs"
|
|
640
|
+
validateOpts(opts, ["rua", "httpClient", "timeoutMs"], "tlsRpt.submit");
|
|
641
641
|
if (!Array.isArray(opts.rua) || opts.rua.length === 0) {
|
|
642
642
|
throw new SmtpPolicyError("smtp/tls-rpt-bad-rua",
|
|
643
643
|
"tlsRpt.submit: opts.rua must be a non-empty array of URIs");
|
|
@@ -418,7 +418,6 @@ function create(config) {
|
|
|
418
418
|
"protocol", "region", "accessKeyId", "secretAccessKey", "sessionToken",
|
|
419
419
|
"endpoint", "pathStyle", "forcePathStyle",
|
|
420
420
|
"allowedProtocols", "allowInternal", "timeoutMs",
|
|
421
|
-
"ca",
|
|
422
421
|
"audit", "observability", "auditSuccess", "auditFailures",
|
|
423
422
|
], "bucketOps");
|
|
424
423
|
if (config.protocol && config.protocol !== "sigv4") {
|
|
@@ -464,7 +463,17 @@ function create(config) {
|
|
|
464
463
|
}
|
|
465
464
|
|
|
466
465
|
function _actor(callerOpts) {
|
|
467
|
-
|
|
466
|
+
// `req` resolves IP / user-agent / userId from the live request;
|
|
467
|
+
// `actor` is an explicit override bag (e.g. { userId: "ops-admin" })
|
|
468
|
+
// for callers that perform a compliance-sensitive bucket change on
|
|
469
|
+
// behalf of an operator and want that identity on the audit row.
|
|
470
|
+
// Passed as the override seed: explicit `actor` fields win over the
|
|
471
|
+
// request-derived ones, while request-derived fields fill any key the
|
|
472
|
+
// operator left unset.
|
|
473
|
+
var seed = (callerOpts && callerOpts.actor && typeof callerOpts.actor === "object")
|
|
474
|
+
? callerOpts.actor
|
|
475
|
+
: null;
|
|
476
|
+
return requestHelpers.resolveActorWithOverride(callerOpts || {}, seed);
|
|
468
477
|
}
|
|
469
478
|
|
|
470
479
|
// S3 subresource queries (`?lifecycle`, `?cors`, `?object-lock`,
|
|
@@ -156,7 +156,7 @@ function create(opts) {
|
|
|
156
156
|
validateOpts(opts, [
|
|
157
157
|
"service", "resource", "scope",
|
|
158
158
|
"maxAttributes", "maxEvents", "maxAttributeValueLength",
|
|
159
|
-
"onEnd", "onStart",
|
|
159
|
+
"onEnd", "onStart",
|
|
160
160
|
], "tracer.create");
|
|
161
161
|
validateOpts.requireNonEmptyString(opts.service,
|
|
162
162
|
"tracer.create: service", TracerError, "tracer/bad-service");
|
package/lib/problem-details.js
CHANGED
|
@@ -132,19 +132,28 @@ function getBase() {
|
|
|
132
132
|
* - `status` (recommended) must be an integer 100..599.
|
|
133
133
|
* - `detail` (optional) must be a string when given.
|
|
134
134
|
* - `instance` (optional) must be a URI reference string when given.
|
|
135
|
-
* - Extensions: every additional key whose name is NOT in
|
|
135
|
+
* - Extensions: every additional top-level key whose name is NOT in
|
|
136
136
|
* `RESERVED_FIELDS` is preserved at the top level. Reserved-name
|
|
137
|
-
* collisions throw `problem-details/reserved-extension
|
|
137
|
+
* collisions throw `problem-details/reserved-extension`;
|
|
138
|
+
* prototype-pollution-shaped top-level keys throw the same.
|
|
139
|
+
* - `extensions`: a plain object whose keys are spread as top-level
|
|
140
|
+
* sibling members (RFC 9457 §3.2) — the literal `extensions`
|
|
141
|
+
* member is never emitted. Keys colliding with `RESERVED_FIELDS`
|
|
142
|
+
* are ignored (reserved fields can't be overridden by an
|
|
143
|
+
* extension); prototype-pollution-shaped keys are dropped
|
|
144
|
+
* silently. When the same name appears both as a direct top-level
|
|
145
|
+
* key and inside `extensions`, the direct top-level key wins.
|
|
138
146
|
*
|
|
139
147
|
* Returns a frozen plain object suitable for `JSON.stringify`.
|
|
140
148
|
*
|
|
141
149
|
* @opts
|
|
142
|
-
* type:
|
|
143
|
-
* title:
|
|
144
|
-
* status:
|
|
145
|
-
* detail:
|
|
146
|
-
* instance:
|
|
147
|
-
*
|
|
150
|
+
* type: string, // problem-type URI reference (default "about:blank")
|
|
151
|
+
* title: string, // short summary
|
|
152
|
+
* status: number, // integer 100..599
|
|
153
|
+
* detail: string, // human-readable explanation
|
|
154
|
+
* instance: string, // URI reference for this specific occurrence
|
|
155
|
+
* extensions: object, // keys spread as top-level siblings (§3.2); direct top-level key wins on collision
|
|
156
|
+
* ...extensions // additional top-level keys preserved as-is
|
|
148
157
|
*
|
|
149
158
|
* @example
|
|
150
159
|
* var p = b.problemDetails.create({
|
|
@@ -213,15 +222,44 @@ function create(opts) {
|
|
|
213
222
|
|
|
214
223
|
// Extensions — every additional key. §3.2 endorses sibling
|
|
215
224
|
// extensions as long as their names don't collide with reserved.
|
|
225
|
+
// The `extensions` key itself is NOT emitted as a literal nested
|
|
226
|
+
// member: a plain-object value is spread so each of its keys lands
|
|
227
|
+
// as a top-level sibling, subject to the same reserved / poisoned
|
|
228
|
+
// guards as direct top-level keys. A direct top-level extension key
|
|
229
|
+
// wins over the same name nested under `extensions`.
|
|
216
230
|
var keys = Object.keys(opts);
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
var directKeys = Object.create(null);
|
|
232
|
+
var i, k;
|
|
233
|
+
for (i = 0; i < keys.length; i += 1) {
|
|
234
|
+
k = keys[i];
|
|
235
|
+
if (k === "extensions") continue;
|
|
219
236
|
if (RESERVED_FIELDS.indexOf(k) !== -1) continue;
|
|
220
237
|
if (POISONED_KEYS.indexOf(k) !== -1) {
|
|
221
238
|
throw new ProblemDetailsError("problem-details/reserved-extension",
|
|
222
239
|
"create: extension key '" + k + "' refused (prototype-pollution shape)", true);
|
|
223
240
|
}
|
|
224
241
|
out[k] = opts[k];
|
|
242
|
+
directKeys[k] = true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Spread `extensions` (RFC 9457 §3.2 sibling members). Reserved
|
|
246
|
+
// names can't be overridden by an extension key; poisoned keys are
|
|
247
|
+
// dropped silently (an inbound extension map is a less-trusted shape
|
|
248
|
+
// than a hand-authored top-level key — a direct poisoned key still
|
|
249
|
+
// throws). A direct top-level key already present wins.
|
|
250
|
+
if (opts.extensions !== undefined && opts.extensions !== null) {
|
|
251
|
+
if (typeof opts.extensions !== "object" || Array.isArray(opts.extensions)) {
|
|
252
|
+
throw new ProblemDetailsError("problem-details/bad-extensions",
|
|
253
|
+
"create: extensions must be a plain object when provided", true);
|
|
254
|
+
}
|
|
255
|
+
var extKeys = Object.keys(opts.extensions);
|
|
256
|
+
for (i = 0; i < extKeys.length; i += 1) {
|
|
257
|
+
k = extKeys[i];
|
|
258
|
+
if (RESERVED_FIELDS.indexOf(k) !== -1) continue;
|
|
259
|
+
if (POISONED_KEYS.indexOf(k) !== -1) continue;
|
|
260
|
+
if (directKeys[k]) continue;
|
|
261
|
+
out[k] = opts.extensions[k];
|
|
262
|
+
}
|
|
225
263
|
}
|
|
226
264
|
|
|
227
265
|
return Object.freeze(out);
|
|
@@ -386,13 +424,20 @@ function respond(res, problem, req) {
|
|
|
386
424
|
* `Cache-Control: no-store` are written; status code defaults to
|
|
387
425
|
* 500 when omitted.
|
|
388
426
|
*
|
|
427
|
+
* `extensions` keys are spread as top-level sibling members (RFC 9457
|
|
428
|
+
* §3.2) via `create` — the literal `extensions` member is never
|
|
429
|
+
* emitted. Keys colliding with the reserved `type` / `title` /
|
|
430
|
+
* `status` / `detail` / `instance` are ignored; prototype-pollution-
|
|
431
|
+
* shaped keys are dropped. A direct top-level key wins over the same
|
|
432
|
+
* name nested under `extensions`.
|
|
433
|
+
*
|
|
389
434
|
* @opts
|
|
390
435
|
* status: number, // HTTP status code (100..599); default 500
|
|
391
436
|
* title: string, // operator-supplied short title
|
|
392
437
|
* detail: string, // operator-supplied human-readable explanation
|
|
393
438
|
* type: string, // problem-type URI (defaults to "about:blank")
|
|
394
439
|
* instance: string, // optional per-occurrence URI
|
|
395
|
-
* extensions: object, //
|
|
440
|
+
* extensions: object, // keys spread as top-level siblings (§3.2); direct top-level key wins on collision
|
|
396
441
|
*
|
|
397
442
|
* @example
|
|
398
443
|
* // Migrating from inline JSON-error shape:
|
package/lib/pubsub-cluster.js
CHANGED
|
@@ -24,18 +24,31 @@
|
|
|
24
24
|
var clusterStorage = require("./cluster-storage");
|
|
25
25
|
var C = require("./constants");
|
|
26
26
|
var lazyRequire = require("./lazy-require");
|
|
27
|
+
var validateOpts = require("./validate-opts");
|
|
28
|
+
var { defineClass } = require("./framework-error");
|
|
27
29
|
|
|
28
30
|
var logger = lazyRequire(function () { return require("./log").boot("pubsub-cluster"); });
|
|
29
31
|
|
|
32
|
+
var PubsubError = defineClass("PubsubError");
|
|
33
|
+
|
|
30
34
|
var DEFAULT_POLL_INTERVAL_MS = 100;
|
|
31
35
|
var DEFAULT_RETENTION_MS = C.TIME.minutes(1);
|
|
32
36
|
var DEFAULT_PRUNE_EVERY_MS = C.TIME.minutes(5);
|
|
33
37
|
|
|
34
38
|
function create(opts) {
|
|
35
39
|
var clusterInstance = opts.cluster;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
// Config-time: a typo (NaN-coercing string / negative / fractional) must
|
|
41
|
+
// surface at create, not silently fall back to the default and ship a
|
|
42
|
+
// mis-tuned poll loop. THROW on present-but-bad; absent keeps the default.
|
|
43
|
+
validateOpts.optionalPositiveInt(opts.pollIntervalMs,
|
|
44
|
+
"pubsub: pollIntervalMs", PubsubError, "BAD_OPT");
|
|
45
|
+
validateOpts.optionalPositiveInt(opts.retentionMs,
|
|
46
|
+
"pubsub: retentionMs", PubsubError, "BAD_OPT");
|
|
47
|
+
validateOpts.optionalPositiveInt(opts.pruneEveryMs,
|
|
48
|
+
"pubsub: pruneEveryMs", PubsubError, "BAD_OPT");
|
|
49
|
+
var pollIntervalMs = opts.pollIntervalMs !== undefined ? opts.pollIntervalMs : DEFAULT_POLL_INTERVAL_MS;
|
|
50
|
+
var retentionMs = opts.retentionMs !== undefined ? opts.retentionMs : DEFAULT_RETENTION_MS;
|
|
51
|
+
var pruneEveryMs = opts.pruneEveryMs !== undefined ? opts.pruneEveryMs : DEFAULT_PRUNE_EVERY_MS;
|
|
39
52
|
|
|
40
53
|
var lastSeenId = 0;
|
|
41
54
|
var primed = false;
|
package/lib/queue-sqs.js
CHANGED
|
@@ -50,6 +50,7 @@ var httpClient = require("./http-client");
|
|
|
50
50
|
var cryptoField = require("./crypto-field");
|
|
51
51
|
var safeJson = require("./safe-json");
|
|
52
52
|
var safeUrl = require("./safe-url");
|
|
53
|
+
var validateOpts = require("./validate-opts");
|
|
53
54
|
var { generateToken } = require("./crypto");
|
|
54
55
|
var { QueueError } = require("./framework-error");
|
|
55
56
|
|
|
@@ -102,8 +103,25 @@ function create(opts) {
|
|
|
102
103
|
var accountId = opts.accountId ? String(opts.accountId) : null;
|
|
103
104
|
var timeoutMs = opts.timeoutMs;
|
|
104
105
|
var allowInternal = opts.allowInternal != null ? opts.allowInternal : null;
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
// Config-time: a typo (NaN-coercing string / negative / fractional)
|
|
107
|
+
// must surface at create, not silently fall back to the default and ship
|
|
108
|
+
// a mis-tuned lease loop. THROW on present-but-bad; absent keeps default.
|
|
109
|
+
validateOpts.optionalPositiveInt(opts.visibilityTimeoutSec,
|
|
110
|
+
"queue-sqs: visibilityTimeoutSec", QueueError, "INVALID_CONFIG");
|
|
111
|
+
// waitTimeSec=0 is the valid SQS short-poll sentinel (the default), so a
|
|
112
|
+
// positive-int check would wrongly reject it — allow non-negative integers.
|
|
113
|
+
if (opts.waitTimeSec !== undefined &&
|
|
114
|
+
(typeof opts.waitTimeSec !== "number" || !isFinite(opts.waitTimeSec) ||
|
|
115
|
+
opts.waitTimeSec < 0 || Math.floor(opts.waitTimeSec) !== opts.waitTimeSec)) {
|
|
116
|
+
throw _err("INVALID_CONFIG",
|
|
117
|
+
"queue-sqs: waitTimeSec must be a non-negative integer (0 = short-poll), got " +
|
|
118
|
+
(typeof opts.waitTimeSec === "number" ? String(opts.waitTimeSec) : typeof opts.waitTimeSec),
|
|
119
|
+
true);
|
|
120
|
+
}
|
|
121
|
+
var visibilityTimeoutSec = opts.visibilityTimeoutSec !== undefined
|
|
122
|
+
? opts.visibilityTimeoutSec : DEFAULT_VISIBILITY_TIMEOUT_SEC;
|
|
123
|
+
var waitTimeSec = opts.waitTimeSec !== undefined
|
|
124
|
+
? opts.waitTimeSec : DEFAULT_WAIT_TIME_SEC;
|
|
107
125
|
|
|
108
126
|
var queueUrlResolver = typeof opts.queueUrlByName === "function"
|
|
109
127
|
? opts.queueUrlByName
|