@blamejs/core 0.14.19 → 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 +4 -0
- package/README.md +1 -1
- package/lib/auth/oauth.js +736 -1
- package/lib/auth/oid4vci.js +124 -5
- package/lib/auth/oid4vp.js +14 -4
- package/lib/auth/sd-jwt-vc-holder.js +46 -1
- package/lib/break-glass.js +1 -2
- package/lib/config.js +28 -31
- package/lib/crypto-field.js +274 -17
- 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-auth.js +333 -0
- 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/fetch-metadata.js +115 -14
- package/lib/middleware/openapi-serve.js +3 -0
- package/lib/middleware/scim-server.js +297 -19
- package/lib/middleware/security-headers.js +47 -0
- 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/observability.js +39 -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
|
}
|
|
@@ -34,6 +34,18 @@
|
|
|
34
34
|
* dnsPrefetchControl: 'off' (default) or 'on' or false
|
|
35
35
|
* csp: '<full CSP string>' or false to disable
|
|
36
36
|
* }
|
|
37
|
+
*
|
|
38
|
+
* Monitor-mode opt-ins (all default-off; unset emits no new header):
|
|
39
|
+
*
|
|
40
|
+
* coopReportOnly / coepReportOnly / documentPolicyReportOnly — set a
|
|
41
|
+
* policy string to emit the matching `*-Report-Only` header so the
|
|
42
|
+
* operator can roll out the enforcing policy in monitor mode first.
|
|
43
|
+
* The browser reports violations (to a Reporting-Endpoints group named
|
|
44
|
+
* in the value, e.g. `same-origin; report-to="coop"`) without blocking.
|
|
45
|
+
* requireDocumentPolicy — the embedder-required Document-Policy a
|
|
46
|
+
* subframe must advertise before this document will embed it.
|
|
47
|
+
* serviceWorkerAllowed — broadens the max scope a service worker
|
|
48
|
+
* registered from this script may claim (the operator opts in).
|
|
37
49
|
*/
|
|
38
50
|
|
|
39
51
|
var requestHelpers = require("../request-helpers");
|
|
@@ -186,6 +198,11 @@ function _validatePermissionsPolicy(value) {
|
|
|
186
198
|
* criticalCh: string|false,
|
|
187
199
|
* reportingEndpoints: object,
|
|
188
200
|
* trustProxy: boolean|number,
|
|
201
|
+
* coopReportOnly: string, // default: off — monitor-mode COOP
|
|
202
|
+
* coepReportOnly: string, // default: off — monitor-mode COEP
|
|
203
|
+
* documentPolicyReportOnly: string, // default: off — monitor-mode Document-Policy
|
|
204
|
+
* requireDocumentPolicy: string, // default: off — embedder-required subframe policy
|
|
205
|
+
* serviceWorkerAllowed: string, // default: off — broadens SW registration scope
|
|
189
206
|
* }
|
|
190
207
|
*
|
|
191
208
|
* @example
|
|
@@ -202,6 +219,8 @@ function create(opts) {
|
|
|
202
219
|
"permissionsPolicy", "coop", "coep", "corp",
|
|
203
220
|
"originAgentCluster", "dnsPrefetchControl", "csp", "trustProxy",
|
|
204
221
|
"reportingEndpoints", "documentPolicy", "criticalCh", "acceptCh",
|
|
222
|
+
"coopReportOnly", "coepReportOnly", "documentPolicyReportOnly",
|
|
223
|
+
"requireDocumentPolicy", "serviceWorkerAllowed",
|
|
205
224
|
], "middleware.securityHeaders");
|
|
206
225
|
if (opts.permissionsPolicy && typeof opts.permissionsPolicy === "string") {
|
|
207
226
|
_validatePermissionsPolicy(opts.permissionsPolicy);
|
|
@@ -222,6 +241,26 @@ function create(opts) {
|
|
|
222
241
|
var docPolicy = opts.documentPolicy === undefined ? DEFAULT_DOCUMENT_POLICY : opts.documentPolicy;
|
|
223
242
|
var criticalCh = opts.criticalCh && typeof opts.criticalCh === "string" ? opts.criticalCh : false;
|
|
224
243
|
var acceptCh = opts.acceptCh && typeof opts.acceptCh === "string" ? opts.acceptCh : false;
|
|
244
|
+
// Monitor-mode + scope opt-ins — all default-off. Each only emits its
|
|
245
|
+
// header when the operator passes a non-empty string; unset = silent.
|
|
246
|
+
// coopReportOnly / coepReportOnly — WHATWG HTML cross-origin isolation
|
|
247
|
+
// report-only variants: the UA evaluates the policy and reports
|
|
248
|
+
// violations to the named Reporting-Endpoints group without
|
|
249
|
+
// enforcing, so an operator can verify a same-origin / require-corp
|
|
250
|
+
// rollout won't break embeds before flipping the enforcing header.
|
|
251
|
+
// documentPolicyReportOnly — W3C Document Policy report-only variant
|
|
252
|
+
// (same monitor-mode semantics for the Document-Policy feature set).
|
|
253
|
+
// requireDocumentPolicy — W3C Document Policy: the policy a subframe
|
|
254
|
+
// must itself advertise (via Document-Policy) before this document
|
|
255
|
+
// will embed it; the embedder declares its floor.
|
|
256
|
+
// serviceWorkerAllowed — W3C Service Workers §Service-Worker-Allowed:
|
|
257
|
+
// widens the max scope a worker registered from this script may
|
|
258
|
+
// claim beyond the script's own path. Operator opts in explicitly.
|
|
259
|
+
var coopReportOnly = opts.coopReportOnly && typeof opts.coopReportOnly === "string" ? opts.coopReportOnly : false;
|
|
260
|
+
var coepReportOnly = opts.coepReportOnly && typeof opts.coepReportOnly === "string" ? opts.coepReportOnly : false;
|
|
261
|
+
var docPolicyReportOnly = opts.documentPolicyReportOnly && typeof opts.documentPolicyReportOnly === "string" ? opts.documentPolicyReportOnly : false;
|
|
262
|
+
var requireDocPolicy = opts.requireDocumentPolicy && typeof opts.requireDocumentPolicy === "string" ? opts.requireDocumentPolicy : false;
|
|
263
|
+
var serviceWorkerAllowed = opts.serviceWorkerAllowed && typeof opts.serviceWorkerAllowed === "string" ? opts.serviceWorkerAllowed : false;
|
|
225
264
|
// Reporting-Endpoints (W3C Reporting API) — when operator passes a
|
|
226
265
|
// map of endpoint-name → URL, we emit `Reporting-Endpoints: name="url",
|
|
227
266
|
// name2="url2", ...` and (when default CSP is in force) append
|
|
@@ -273,6 +312,14 @@ function create(opts) {
|
|
|
273
312
|
if (acceptCh) res.setHeader("Accept-CH", acceptCh);
|
|
274
313
|
if (criticalCh) res.setHeader("Critical-CH", criticalCh);
|
|
275
314
|
if (reportingEndpoints) res.setHeader("Reporting-Endpoints", reportingEndpoints);
|
|
315
|
+
// Monitor-mode + scope opt-ins — emitted only when the operator set
|
|
316
|
+
// the corresponding opt; the enforcing COOP/COEP/Document-Policy
|
|
317
|
+
// headers above are unaffected.
|
|
318
|
+
if (coopReportOnly) res.setHeader("Cross-Origin-Opener-Policy-Report-Only", coopReportOnly);
|
|
319
|
+
if (coepReportOnly) res.setHeader("Cross-Origin-Embedder-Policy-Report-Only", coepReportOnly);
|
|
320
|
+
if (docPolicyReportOnly) res.setHeader("Document-Policy-Report-Only", docPolicyReportOnly);
|
|
321
|
+
if (requireDocPolicy) res.setHeader("Require-Document-Policy", requireDocPolicy);
|
|
322
|
+
if (serviceWorkerAllowed) res.setHeader("Service-Worker-Allowed", serviceWorkerAllowed);
|
|
276
323
|
next();
|
|
277
324
|
};
|
|
278
325
|
}
|
|
@@ -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/observability.js
CHANGED
|
@@ -309,7 +309,12 @@ function timed(name, fn, labels) {
|
|
|
309
309
|
// pass attribute keys that should match the keys below. The map is
|
|
310
310
|
// frozen — adding a new attribute requires a release.
|
|
311
311
|
//
|
|
312
|
-
//
|
|
312
|
+
// References:
|
|
313
|
+
// https://opentelemetry.io/docs/specs/semconv/general/attributes/
|
|
314
|
+
// https://opentelemetry.io/docs/specs/semconv/resource/ (resource,
|
|
315
|
+
// telemetry-sdk, deployment-environment)
|
|
316
|
+
// https://opentelemetry.io/docs/specs/semconv/resource/k8s/
|
|
317
|
+
// https://opentelemetry.io/docs/specs/semconv/resource/faas/
|
|
313
318
|
var SEMCONV = Object.freeze({
|
|
314
319
|
// HTTP server (stable per OTel semconv)
|
|
315
320
|
HTTP_REQUEST_METHOD: "http.request.method",
|
|
@@ -370,10 +375,33 @@ var SEMCONV = Object.freeze({
|
|
|
370
375
|
SERVICE_NAME: "service.name",
|
|
371
376
|
SERVICE_VERSION: "service.version",
|
|
372
377
|
SERVICE_INSTANCE_ID: "service.instance.id",
|
|
378
|
+
// peer.service — logical name of the remote service a span talks to,
|
|
379
|
+
// distinct from server.address (the host). OTel semconv (general).
|
|
380
|
+
PEER_SERVICE: "peer.service",
|
|
381
|
+
// Deployment environment (aka deployment tier: "production",
|
|
382
|
+
// "staging"). The bare `deployment.environment` key was deprecated in
|
|
383
|
+
// favour of `deployment.environment.name`; this carries the current
|
|
384
|
+
// stable key. OTel semconv resource/deployment-environment.
|
|
385
|
+
DEPLOYMENT_ENVIRONMENT_NAME: "deployment.environment.name",
|
|
373
386
|
// Telemetry SDK self-identification
|
|
374
387
|
TELEMETRY_SDK_NAME: "telemetry.sdk.name",
|
|
375
388
|
TELEMETRY_SDK_LANGUAGE: "telemetry.sdk.language",
|
|
376
389
|
TELEMETRY_SDK_VERSION: "telemetry.sdk.version",
|
|
390
|
+
// Telemetry distribution self-identification — the redistribution of
|
|
391
|
+
// an OTel SDK an operator runs (e.g. a vendor distro). OTel semconv
|
|
392
|
+
// resource/telemetry-sdk.
|
|
393
|
+
TELEMETRY_DISTRO_NAME: "telemetry.distro.name",
|
|
394
|
+
TELEMETRY_DISTRO_VERSION: "telemetry.distro.version",
|
|
395
|
+
// Instrumentation scope self-identification — the scope (library)
|
|
396
|
+
// that produced a span/metric. OTel semconv otel namespace.
|
|
397
|
+
OTEL_SCOPE_NAME: "otel.scope.name",
|
|
398
|
+
OTEL_SCOPE_VERSION: "otel.scope.version",
|
|
399
|
+
// FaaS (serverless) — function-as-a-service execution context. OTel
|
|
400
|
+
// semconv resource/faas + attributes-registry/faas.
|
|
401
|
+
FAAS_NAME: "faas.name",
|
|
402
|
+
FAAS_VERSION: "faas.version",
|
|
403
|
+
FAAS_INSTANCE: "faas.instance",
|
|
404
|
+
FAAS_TRIGGER: "faas.trigger",
|
|
377
405
|
// GenAI — OpenTelemetry semantic conventions for generative AI
|
|
378
406
|
// workloads (LLM clients, vector DB queries, agent frameworks).
|
|
379
407
|
// Tracking the otel-spec experimental namespace; covers the stable
|
|
@@ -410,9 +438,19 @@ var SEMCONV = Object.freeze({
|
|
|
410
438
|
CONTAINER_ID: "container.id",
|
|
411
439
|
CONTAINER_IMAGE_NAME: "container.image.name",
|
|
412
440
|
CONTAINER_IMAGE_TAG: "container.image.tag",
|
|
441
|
+
// Kubernetes — OTel semconv resource/k8s. Namespace / pod /
|
|
442
|
+
// deployment plus the surrounding workload + node + cluster context.
|
|
413
443
|
K8S_NAMESPACE_NAME: "k8s.namespace.name",
|
|
414
444
|
K8S_POD_NAME: "k8s.pod.name",
|
|
415
445
|
K8S_DEPLOYMENT_NAME: "k8s.deployment.name",
|
|
446
|
+
K8S_NODE_NAME: "k8s.node.name",
|
|
447
|
+
K8S_CLUSTER_NAME: "k8s.cluster.name",
|
|
448
|
+
K8S_CONTAINER_NAME: "k8s.container.name",
|
|
449
|
+
K8S_STATEFULSET_NAME: "k8s.statefulset.name",
|
|
450
|
+
K8S_DAEMONSET_NAME: "k8s.daemonset.name",
|
|
451
|
+
K8S_JOB_NAME: "k8s.job.name",
|
|
452
|
+
K8S_CRONJOB_NAME: "k8s.cronjob.name",
|
|
453
|
+
K8S_REPLICASET_NAME: "k8s.replicaset.name",
|
|
416
454
|
});
|
|
417
455
|
|
|
418
456
|
// W3C Trace Context — parse / build the `traceparent` HTTP header
|