@fedify/fedify 2.3.0-dev.1110 → 2.3.0-dev.1114

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.
Files changed (95) hide show
  1. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DQP-q39h.mjs} +27 -2
  2. package/dist/{builder-B-Y6fwSu.mjs → builder-YlEusQth.mjs} +3 -3
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/outgoing-jsonld.test.mjs +1 -1
  6. package/dist/compat/public-audience.test.mjs +1 -1
  7. package/dist/compat/transformers.test.mjs +2 -2
  8. package/dist/{context-C0C_sRha.d.cts → context-Ch-ZLyTQ.d.cts} +1 -1
  9. package/dist/{context-Dqgt8saU.d.ts → context-cSUMk2da.d.ts} +1 -1
  10. package/dist/{deno-hqC7tKJn.mjs → deno-CF3jMgip.mjs} +1 -1
  11. package/dist/{docloader-BOEuuXkX.mjs → docloader-BENj6vQ4.mjs} +2 -2
  12. package/dist/federation/builder.test.mjs +3 -3
  13. package/dist/federation/collection.test.mjs +2 -2
  14. package/dist/federation/handler.test.mjs +8 -7
  15. package/dist/federation/idempotency.test.mjs +5 -5
  16. package/dist/federation/inbox.test.mjs +1 -1
  17. package/dist/federation/keycache.test.mjs +1 -1
  18. package/dist/federation/kv.test.mjs +2 -2
  19. package/dist/federation/middleware.test.mjs +10 -10
  20. package/dist/federation/mod.cjs +1 -1
  21. package/dist/federation/mod.d.cts +2 -2
  22. package/dist/federation/mod.d.ts +2 -2
  23. package/dist/federation/mod.js +1 -1
  24. package/dist/federation/mq.test.mjs +2 -2
  25. package/dist/federation/negotiation.test.mjs +2 -2
  26. package/dist/federation/router.test.mjs +2 -2
  27. package/dist/federation/send.test.mjs +11 -11
  28. package/dist/federation/webfinger.test.mjs +3 -3
  29. package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
  30. package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
  31. package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
  32. package/dist/{http-BLopFpvC.mjs → http-BmOZYc-8.mjs} +86 -37
  33. package/dist/{http-DV0il3vk.cjs → http-CKCgOPkX.cjs} +427 -35
  34. package/dist/{http-O8MYWwk8.js → http-CpzZ9zsb.js} +393 -37
  35. package/dist/{http-BDZeS5om.d.ts → http-D6LP89UO.d.ts} +7 -1
  36. package/dist/{http-C87EWkO0.d.cts → http-D6aw3j2U.d.cts} +7 -1
  37. package/dist/{key-DW1EVmtP.mjs → key-B4I8H5Lc.mjs} +1 -1
  38. package/dist/{kv-cache-Dya-TWMe.cjs → kv-cache-DY-XWOqM.cjs} +1 -1
  39. package/dist/{kv-cache-C3NWWiTg.js → kv-cache-Wc5ezcVW.js} +1 -1
  40. package/dist/{ld-BNkk2Yal.mjs → ld-B5D5THhl.mjs} +60 -9
  41. package/dist/{send-hokVCPu6.mjs → metrics-ek3ilf6c.mjs} +53 -221
  42. package/dist/{middleware-CjzI3aYo.js → middleware-CuZbBw-N.js} +16 -269
  43. package/dist/{middleware-DA2WTBr4.mjs → middleware-DlcecZMq.mjs} +29 -23
  44. package/dist/{middleware-D6FbOjuK.mjs → middleware-EI7OU6BR.mjs} +1 -1
  45. package/dist/{middleware-DUWeXjZR.cjs → middleware-EqTYPG4F.cjs} +45 -298
  46. package/dist/{mod-DXY9JF28.d.cts → mod-B-Lin9Sy.d.ts} +25 -2
  47. package/dist/{mod-DHO9lk3D.d.ts → mod-BDhgfjP7.d.cts} +25 -2
  48. package/dist/{mod-B0rWmfW5.d.cts → mod-BR_BB0bh.d.cts} +1 -1
  49. package/dist/{mod-Dx3-hqyo.d.ts → mod-C6E8rkcz.d.ts} +1 -1
  50. package/dist/{mod-BhU_H1I_.d.ts → mod-DLrRb0dx.d.ts} +1 -1
  51. package/dist/{mod-CLPnQPsv.d.cts → mod-P9tE2WmM.d.cts} +1 -1
  52. package/dist/mod.cjs +4 -4
  53. package/dist/mod.d.cts +5 -5
  54. package/dist/mod.d.ts +5 -5
  55. package/dist/mod.js +4 -4
  56. package/dist/nodeinfo/client.test.mjs +2 -2
  57. package/dist/nodeinfo/handler.test.mjs +3 -3
  58. package/dist/nodeinfo/types.test.mjs +2 -2
  59. package/dist/otel/exporter.test.mjs +2 -2
  60. package/dist/{outgoing-jsonld-BgFLCJQ_.mjs → outgoing-jsonld-BNL8AC14.mjs} +1 -1
  61. package/dist/{owner-jvJAtR5O.mjs → owner-DO810N24.mjs} +2 -2
  62. package/dist/{proof-mfmHH9j0.mjs → proof-BgfyWv7b.mjs} +25 -7
  63. package/dist/{proof-BD92WeqV.cjs → proof-DIoqrKnX.cjs} +78 -11
  64. package/dist/{proof-5kT7OUPV.js → proof-Vd8-1EWh.js} +78 -11
  65. package/dist/send-CAYXdUTk.mjs +225 -0
  66. package/dist/sig/accept.test.mjs +1 -1
  67. package/dist/sig/http.test.mjs +212 -6
  68. package/dist/sig/key.test.mjs +4 -4
  69. package/dist/sig/ld.test.mjs +138 -5
  70. package/dist/sig/mod.cjs +2 -2
  71. package/dist/sig/mod.d.cts +2 -2
  72. package/dist/sig/mod.d.ts +2 -2
  73. package/dist/sig/mod.js +2 -2
  74. package/dist/sig/owner.test.mjs +4 -4
  75. package/dist/sig/proof.test.mjs +167 -6
  76. package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BTEgfoJo.mjs} +2 -27
  77. package/dist/utils/docloader.test.mjs +5 -5
  78. package/dist/utils/kv-cache.test.mjs +1 -1
  79. package/dist/utils/mod.cjs +1 -1
  80. package/dist/utils/mod.d.cts +1 -1
  81. package/dist/utils/mod.d.ts +1 -1
  82. package/dist/utils/mod.js +1 -1
  83. package/package.json +5 -5
  84. /package/dist/{accept-CceiKpCy.mjs → accept-CgDcxvjV.mjs} +0 -0
  85. /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-BeTGV3wc.mjs} +0 -0
  86. /package/dist/{client-B_A6mfn3.mjs → client-Bneh_DYR.mjs} +0 -0
  87. /package/dist/{collection-CA3V5zyK.mjs → collection-Cc3DVAhE.mjs} +0 -0
  88. /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
  89. /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
  90. /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
  91. /package/dist/{keys-C3kae-6B.mjs → keys-CSYsOMFG.mjs} +0 -0
  92. /package/dist/{kv-x2IvBUyq.mjs → kv-QHE0oeM3.mjs} +0 -0
  93. /package/dist/{kv-cache-CiiNwT6W.mjs → kv-cache-DihufyAQ.mjs} +0 -0
  94. /package/dist/{public-audience-N3pyOx2p.mjs → public-audience-c9zmYKgA.mjs} +0 -0
  95. /package/dist/{types-BFowWFTT.mjs → types-D09GN0uZ.mjs} +0 -0
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
11
11
  let byte_encodings_base64 = require("byte-encodings/base64");
12
12
  //#region deno.json
13
13
  var name = "@fedify/fedify";
14
- var version = "2.3.0-dev.1110+3ba84466";
14
+ var version = "2.3.0-dev.1114+15a3316d";
15
15
  //#endregion
16
16
  //#region src/sig/accept.ts
17
17
  /**
@@ -152,6 +152,314 @@ function fulfillAcceptSignature(entry, localKeyId, localAlg) {
152
152
  };
153
153
  }
154
154
  //#endregion
155
+ //#region src/federation/metrics.ts
156
+ var FederationMetrics = class {
157
+ deliverySent;
158
+ deliveryPermanentFailure;
159
+ signatureVerificationFailure;
160
+ signatureVerificationDuration;
161
+ signatureKeyFetchDuration;
162
+ deliveryDuration;
163
+ inboxProcessingDuration;
164
+ httpServerRequestCount;
165
+ httpServerRequestDuration;
166
+ queueTaskEnqueued;
167
+ queueTaskStarted;
168
+ queueTaskCompleted;
169
+ queueTaskFailed;
170
+ queueTaskDuration;
171
+ queueTaskInFlight;
172
+ constructor(meterProvider) {
173
+ const meter = meterProvider.getMeter(name, version);
174
+ this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
175
+ description: "ActivityPub delivery attempts.",
176
+ unit: "{attempt}"
177
+ });
178
+ this.deliveryPermanentFailure = meter.createCounter("activitypub.delivery.permanent_failure", {
179
+ description: "ActivityPub deliveries abandoned as permanent failures.",
180
+ unit: "{failure}"
181
+ });
182
+ this.signatureVerificationFailure = meter.createCounter("activitypub.signature.verification_failure", {
183
+ description: "ActivityPub signature verification failures.",
184
+ unit: "{failure}"
185
+ });
186
+ this.signatureVerificationDuration = meter.createHistogram("activitypub.signature.verification.duration", {
187
+ description: "Duration of ActivityPub signature verification, including local key lookup and remote key fetches.",
188
+ unit: "ms"
189
+ });
190
+ this.signatureKeyFetchDuration = meter.createHistogram("activitypub.signature.key_fetch.duration", {
191
+ description: "Duration of public key lookup performed during ActivityPub signature verification.",
192
+ unit: "ms"
193
+ });
194
+ this.deliveryDuration = meter.createHistogram("activitypub.delivery.duration", {
195
+ description: "Duration of ActivityPub delivery attempts.",
196
+ unit: "ms"
197
+ });
198
+ this.inboxProcessingDuration = meter.createHistogram("activitypub.inbox.processing_duration", {
199
+ description: "Duration of ActivityPub inbox listener processing.",
200
+ unit: "ms"
201
+ });
202
+ this.httpServerRequestCount = meter.createCounter("fedify.http.server.request.count", {
203
+ description: "HTTP requests handled by Federation.fetch().",
204
+ unit: "{request}"
205
+ });
206
+ this.httpServerRequestDuration = meter.createHistogram("fedify.http.server.request.duration", {
207
+ description: "Duration of HTTP requests handled by Federation.fetch().",
208
+ unit: "ms",
209
+ advice: { explicitBucketBoundaries: [
210
+ 5,
211
+ 10,
212
+ 25,
213
+ 50,
214
+ 75,
215
+ 100,
216
+ 250,
217
+ 500,
218
+ 750,
219
+ 1e3,
220
+ 2500,
221
+ 5e3,
222
+ 7500,
223
+ 1e4
224
+ ] }
225
+ });
226
+ this.queueTaskEnqueued = meter.createCounter("fedify.queue.task.enqueued", {
227
+ description: "Tasks Fedify enqueued for inbox, outbox, or fanout work.",
228
+ unit: "{task}"
229
+ });
230
+ this.queueTaskStarted = meter.createCounter("fedify.queue.task.started", {
231
+ description: "Tasks Fedify began processing as a queue worker.",
232
+ unit: "{task}"
233
+ });
234
+ this.queueTaskCompleted = meter.createCounter("fedify.queue.task.completed", {
235
+ description: "Queue tasks Fedify finished processing without throwing.",
236
+ unit: "{task}"
237
+ });
238
+ this.queueTaskFailed = meter.createCounter("fedify.queue.task.failed", {
239
+ description: "Queue tasks Fedify abandoned because processing threw.",
240
+ unit: "{task}"
241
+ });
242
+ this.queueTaskDuration = meter.createHistogram("fedify.queue.task.duration", {
243
+ description: "Duration of queue task processing in Fedify workers.",
244
+ unit: "ms",
245
+ advice: { explicitBucketBoundaries: [
246
+ 5,
247
+ 10,
248
+ 25,
249
+ 50,
250
+ 75,
251
+ 100,
252
+ 250,
253
+ 500,
254
+ 750,
255
+ 1e3,
256
+ 2500,
257
+ 5e3,
258
+ 7500,
259
+ 1e4
260
+ ] }
261
+ });
262
+ this.queueTaskInFlight = meter.createUpDownCounter("fedify.queue.task.in_flight", {
263
+ description: "Queue tasks currently being processed in this Fedify process.",
264
+ unit: "{task}"
265
+ });
266
+ }
267
+ recordDelivery(inbox, durationMs, success, activityType) {
268
+ const deliveryAttributes = {
269
+ "activitypub.remote.host": getRemoteHost(inbox),
270
+ "activitypub.delivery.success": success
271
+ };
272
+ if (activityType != null) deliveryAttributes["activitypub.activity.type"] = activityType;
273
+ this.deliverySent.add(1, deliveryAttributes);
274
+ this.deliveryDuration.record(durationMs, deliveryAttributes);
275
+ }
276
+ recordPermanentFailure(inbox, statusCode) {
277
+ this.deliveryPermanentFailure.add(1, {
278
+ "activitypub.remote.host": getRemoteHost(inbox),
279
+ "http.response.status_code": statusCode
280
+ });
281
+ }
282
+ recordSignatureVerificationFailure(reason, remoteHost) {
283
+ const attributes = { "activitypub.verification.failure_reason": reason };
284
+ if (remoteHost != null) attributes["activitypub.remote.host"] = remoteHost;
285
+ this.signatureVerificationFailure.add(1, attributes);
286
+ }
287
+ recordSignatureVerificationDuration(durationMs, kind, result, extra = {}) {
288
+ const attributes = {
289
+ "activitypub.signature.kind": kind,
290
+ "activitypub.signature.result": result
291
+ };
292
+ if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
293
+ if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
294
+ if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
295
+ if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
296
+ this.signatureVerificationDuration.record(durationMs, attributes);
297
+ }
298
+ recordSignatureKeyFetchDuration(durationMs, kind, result) {
299
+ this.signatureKeyFetchDuration.record(durationMs, {
300
+ "activitypub.signature.kind": kind,
301
+ "activitypub.signature.key_fetch.result": result
302
+ });
303
+ }
304
+ recordInboxProcessingDuration(activityType, durationMs) {
305
+ this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
306
+ }
307
+ recordHttpServerRequest(method, endpoint, durationMs, options = {}) {
308
+ const attributes = {
309
+ "http.request.method": normalizeHttpMethod(method),
310
+ "fedify.endpoint": endpoint
311
+ };
312
+ if (options.statusCode != null) attributes["http.response.status_code"] = options.statusCode;
313
+ if (options.routeTemplate != null) attributes["fedify.route.template"] = options.routeTemplate;
314
+ this.httpServerRequestCount.add(1, attributes);
315
+ this.httpServerRequestDuration.record(durationMs, attributes);
316
+ }
317
+ recordQueueTaskEnqueued(common, attempt) {
318
+ const attributes = buildQueueTaskAttributes(common);
319
+ attributes["fedify.queue.task.attempt"] = attempt;
320
+ this.queueTaskEnqueued.add(1, attributes);
321
+ }
322
+ recordQueueTaskStarted(common) {
323
+ this.queueTaskStarted.add(1, buildQueueTaskAttributes(common));
324
+ }
325
+ incrementQueueTaskInFlight(common) {
326
+ this.queueTaskInFlight.add(1, buildQueueTaskInFlightAttributes(common));
327
+ }
328
+ decrementQueueTaskInFlight(common) {
329
+ this.queueTaskInFlight.add(-1, buildQueueTaskInFlightAttributes(common));
330
+ }
331
+ recordQueueTaskOutcome(common, result, durationMs) {
332
+ const attributes = buildQueueTaskAttributes(common);
333
+ attributes["fedify.queue.task.result"] = result;
334
+ if (result === "completed") this.queueTaskCompleted.add(1, attributes);
335
+ else if (result === "failed") this.queueTaskFailed.add(1, attributes);
336
+ this.queueTaskDuration.record(durationMs, attributes);
337
+ }
338
+ };
339
+ function buildQueueTaskAttributes(common) {
340
+ const attributes = { "fedify.queue.role": common.role };
341
+ const backend = getQueueBackend(common.queue);
342
+ if (backend != null) attributes["fedify.queue.backend"] = backend;
343
+ const nativeRetrial = common.queue?.nativeRetrial;
344
+ if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
345
+ if (common.activityType != null) attributes["activitypub.activity.type"] = common.activityType;
346
+ return attributes;
347
+ }
348
+ function buildQueueTaskInFlightAttributes(common) {
349
+ return buildQueueTaskAttributes({
350
+ role: common.role,
351
+ queue: common.queue
352
+ });
353
+ }
354
+ /**
355
+ * Returns the constructor name of the given message queue, when it is a
356
+ * meaningful identifier. Used as a best-effort `fedify.queue.backend`
357
+ * attribute on queue task metrics; returns `undefined` for plain object
358
+ * literals (whose constructor is `Object`) so the attribute does not appear
359
+ * with a non-informative value.
360
+ * @since 2.3.0
361
+ */
362
+ function getQueueBackend(queue) {
363
+ const name = queue?.constructor?.name;
364
+ if (name == null || name === "" || name === "Object") return void 0;
365
+ return name;
366
+ }
367
+ /**
368
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue.
369
+ *
370
+ * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
371
+ * outbox messages with the same metric attributes (role, queue, activity
372
+ * type, attempt), so they share this helper rather than each defining a local
373
+ * closure.
374
+ * @since 2.3.0
375
+ */
376
+ function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
377
+ getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
378
+ role: "outbox",
379
+ queue: outboxQueue,
380
+ activityType: message.activityType
381
+ }, message.attempt);
382
+ }
383
+ /**
384
+ * Times an awaited public key fetch and records exactly one
385
+ * `activitypub.signature.key_fetch.duration` measurement, classifying the
386
+ * outcome as `hit`, `fetched`, or `error` based on the `cached` flag and
387
+ * whether the returned key is non-null. Errors thrown by the fetch are
388
+ * reported as `error` and rethrown, so verifier behavior is unchanged.
389
+ *
390
+ * Shared by the three signature verifiers (HTTP, Linked Data, Object
391
+ * Integrity Proofs); the only per-call variation is the
392
+ * `activitypub.signature.kind` attribute value.
393
+ * @since 2.3.0
394
+ */
395
+ async function measureSignatureKeyFetch(meterProvider, kind, fetch) {
396
+ const start = performance.now();
397
+ try {
398
+ const result = await fetch();
399
+ getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, result.key != null ? result.cached ? "hit" : "fetched" : "error");
400
+ return result;
401
+ } catch (error) {
402
+ getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, "error");
403
+ throw error;
404
+ }
405
+ }
406
+ /**
407
+ * Whether the given thrown value is an `AbortError`.
408
+ *
409
+ * `processQueuedTask` distinguishes aborted tasks (recorded as
410
+ * `fedify.queue.task.result=aborted`) from other failures so that backend
411
+ * shutdown signals do not inflate the `fedify.queue.task.failed` counter.
412
+ * @since 2.3.0
413
+ */
414
+ function isAbortError$1(error) {
415
+ if (error == null || typeof error !== "object") return false;
416
+ const name = error.name;
417
+ return typeof name === "string" && name === "AbortError";
418
+ }
419
+ const KNOWN_HTTP_METHODS = new Set([
420
+ "CONNECT",
421
+ "DELETE",
422
+ "GET",
423
+ "HEAD",
424
+ "OPTIONS",
425
+ "PATCH",
426
+ "POST",
427
+ "PUT",
428
+ "QUERY",
429
+ "TRACE"
430
+ ]);
431
+ function normalizeHttpMethod(method) {
432
+ const upper = method.toUpperCase();
433
+ return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
434
+ }
435
+ const federationMetrics = /* @__PURE__ */ new WeakMap();
436
+ /**
437
+ * Gets the cached Fedify metric instruments for a meter provider.
438
+ * @since 2.3.0
439
+ */
440
+ function getFederationMetrics(meterProvider = _opentelemetry_api.metrics.getMeterProvider()) {
441
+ let instruments = federationMetrics.get(meterProvider);
442
+ if (instruments == null) {
443
+ instruments = new FederationMetrics(meterProvider);
444
+ federationMetrics.set(meterProvider, instruments);
445
+ }
446
+ return instruments;
447
+ }
448
+ /**
449
+ * Gets the bounded remote host attribute value for a URL.
450
+ * @since 2.3.0
451
+ */
452
+ function getRemoteHost(url) {
453
+ return url.hostname;
454
+ }
455
+ /**
456
+ * Gets an elapsed duration in milliseconds from a `performance.now()` value.
457
+ * @since 2.3.0
458
+ */
459
+ function getDurationMs(start) {
460
+ return Math.max(0, performance.now() - start);
461
+ }
462
+ //#endregion
155
463
  //#region src/sig/key.ts
156
464
  /**
157
465
  * Checks if the given key is valid and supported. No-op if the key is valid,
@@ -800,6 +1108,27 @@ function parseKeyId(value) {
800
1108
  function getKeyFetchErrorName(error) {
801
1109
  return error.name || error.constructor.name || "Error";
802
1110
  }
1111
+ /**
1112
+ * Known draft-cavage `algorithm` parameter values, used to keep the
1113
+ * `http_signatures.algorithm` metric attribute on a bounded set. The header
1114
+ * field is attacker-controlled and not used to select the verification
1115
+ * algorithm, so unknown values are dropped from the metric to prevent
1116
+ * cardinality blow-up.
1117
+ */
1118
+ const DRAFT_KNOWN_ALGORITHMS = new Set([
1119
+ "ecdsa-sha256",
1120
+ "ecdsa-sha384",
1121
+ "ecdsa-sha512",
1122
+ "ed25519",
1123
+ "hs2019",
1124
+ "rsa-sha1",
1125
+ "rsa-sha256",
1126
+ "rsa-sha512"
1127
+ ]);
1128
+ function classifyHttpVerifyResult(result) {
1129
+ if (result.verified) return "verified";
1130
+ return result.reason.type === "noSignature" ? "missing" : "rejected";
1131
+ }
803
1132
  function recordVerificationResult(span, result) {
804
1133
  span.setAttribute("http_signatures.verified", result.verified);
805
1134
  if (result.verified === true) return;
@@ -843,27 +1172,37 @@ async function verifyRequestDetailed(request, options = {}) {
843
1172
  span.setAttribute(_opentelemetry_semantic_conventions.ATTR_URL_FULL, request.url);
844
1173
  for (const [name, value] of request.headers) span.setAttribute((0, _opentelemetry_semantic_conventions.ATTR_HTTP_REQUEST_HEADER)(name), value);
845
1174
  }
1175
+ const start = performance.now();
1176
+ const metricsContext = {};
1177
+ let result;
1178
+ let threw = false;
846
1179
  try {
847
1180
  let spec = options.spec;
848
1181
  if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
849
- let result;
850
- if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, options);
851
- else result = await verifyRequestDraft(request, span, options);
1182
+ if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
1183
+ else result = await verifyRequestDraft(request, span, metricsContext, options);
852
1184
  recordVerificationResult(span, result);
853
1185
  if (!result.verified) span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
854
1186
  return result;
855
1187
  } catch (error) {
1188
+ threw = true;
856
1189
  span.setStatus({
857
1190
  code: _opentelemetry_api.SpanStatusCode.ERROR,
858
1191
  message: String(error)
859
1192
  });
860
1193
  throw error;
861
1194
  } finally {
1195
+ const classified = threw ? "error" : classifyHttpVerifyResult(result);
1196
+ const failureReason = result != null && !result.verified && result.reason.type !== "noSignature" ? result.reason.type : void 0;
1197
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "http", classified, {
1198
+ algorithm: metricsContext.algorithm,
1199
+ failureReason
1200
+ });
862
1201
  span.end();
863
1202
  }
864
1203
  });
865
1204
  }
866
- async function verifyRequestDraft(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
1205
+ async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
867
1206
  const logger = (0, _logtape_logtape.getLogger)([
868
1207
  "fedify",
869
1208
  "sig",
@@ -1011,13 +1350,17 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1011
1350
  const keyIdUrl = parseKeyId(keyId);
1012
1351
  if (keyIdUrl == null) return invalidSignatureResult(null);
1013
1352
  span?.setAttribute("http_signatures.key_id", keyId);
1014
- if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
1015
- const { key, cached, fetchError } = await fetchKeyDetailed(keyIdUrl, _fedify_vocab.CryptographicKey, {
1353
+ if ("algorithm" in sigValues) {
1354
+ span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
1355
+ const normalizedAlgorithm = sigValues.algorithm.toLowerCase();
1356
+ if (DRAFT_KNOWN_ALGORITHMS.has(normalizedAlgorithm)) metricsContext.algorithm = normalizedAlgorithm;
1357
+ }
1358
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyIdUrl, _fedify_vocab.CryptographicKey, {
1016
1359
  documentLoader,
1017
1360
  contextLoader,
1018
1361
  keyCache,
1019
1362
  tracerProvider
1020
- });
1363
+ }));
1021
1364
  if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
1022
1365
  if (key == null) return invalidSignatureResult(keyIdUrl);
1023
1366
  const headerNames = headers.split(/\s+/g);
@@ -1039,7 +1382,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1039
1382
  signature,
1040
1383
  message
1041
1384
  });
1042
- return await verifyRequestDetailed(originalRequest, {
1385
+ return await verifyRequestDraft(originalRequest, span, metricsContext, {
1043
1386
  documentLoader,
1044
1387
  contextLoader,
1045
1388
  timeWindow,
@@ -1047,7 +1390,9 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1047
1390
  keyCache: {
1048
1391
  get: () => Promise.resolve(void 0),
1049
1392
  set: async (keyId, key) => await keyCache?.set(keyId, key)
1050
- }
1393
+ },
1394
+ meterProvider,
1395
+ tracerProvider
1051
1396
  });
1052
1397
  }
1053
1398
  logger.debug("Failed to verify with the fetched key {keyId}; signature {signature} is invalid. Check if the key is correct or if the signed message is correct. The message to sign is:\n{message}", {
@@ -1123,7 +1468,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
1123
1468
  }
1124
1469
  return false;
1125
1470
  }
1126
- async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
1471
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
1127
1472
  const logger = (0, _logtape_logtape.getLogger)([
1128
1473
  "fedify",
1129
1474
  "sig",
@@ -1157,9 +1502,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1157
1502
  return invalidSignatureResult(null);
1158
1503
  }
1159
1504
  let failure = noSignatureResult();
1505
+ let failureAlgorithm;
1506
+ const setFailure = (result, algorithm) => {
1507
+ failure = result;
1508
+ failureAlgorithm = algorithm;
1509
+ };
1160
1510
  for (const sigName of signatureNames) {
1161
1511
  if (!signatures[sigName]) {
1162
- failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
1512
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
1163
1513
  continue;
1164
1514
  }
1165
1515
  const sigInput = signatureInputs[sigName];
@@ -1170,7 +1520,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1170
1520
  signatureName: sigName,
1171
1521
  signatureInput: signatureInputHeader
1172
1522
  });
1173
- failure = invalidSignatureResult(null);
1523
+ setFailure(invalidSignatureResult(null));
1174
1524
  continue;
1175
1525
  }
1176
1526
  if (!sigInput.created) {
@@ -1178,7 +1528,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1178
1528
  signatureName: sigName,
1179
1529
  signatureInput: signatureInputHeader
1180
1530
  });
1181
- failure = invalidSignatureResult(keyId);
1531
+ setFailure(invalidSignatureResult(keyId));
1182
1532
  continue;
1183
1533
  }
1184
1534
  const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
@@ -1190,14 +1540,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1190
1540
  created: signatureCreated.toString(),
1191
1541
  now: now.toString()
1192
1542
  });
1193
- failure = invalidSignatureResult(keyId);
1543
+ setFailure(invalidSignatureResult(keyId));
1194
1544
  continue;
1195
1545
  } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
1196
1546
  logger.debug("Failed to verify; signature created time is too far in the past.", {
1197
1547
  created: signatureCreated.toString(),
1198
1548
  now: now.toString()
1199
1549
  });
1200
- failure = invalidSignatureResult(keyId);
1550
+ setFailure(invalidSignatureResult(keyId));
1201
1551
  continue;
1202
1552
  }
1203
1553
  }
@@ -1205,34 +1555,34 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1205
1555
  const contentDigestHeader = request.headers.get("Content-Digest");
1206
1556
  if (!contentDigestHeader) {
1207
1557
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
1208
- failure = invalidSignatureResult(keyId);
1558
+ setFailure(invalidSignatureResult(keyId));
1209
1559
  continue;
1210
1560
  }
1211
1561
  if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
1212
1562
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1213
- failure = invalidSignatureResult(keyId);
1563
+ setFailure(invalidSignatureResult(keyId));
1214
1564
  continue;
1215
1565
  }
1216
1566
  }
1217
1567
  span?.setAttribute("http_signatures.key_id", sigInput.keyId);
1218
1568
  span?.setAttribute("http_signatures.created", sigInput.created.toString());
1219
1569
  if (keyId == null) {
1220
- failure = invalidSignatureResult(null);
1570
+ setFailure(invalidSignatureResult(null));
1221
1571
  continue;
1222
1572
  }
1223
- const { key, cached, fetchError } = await fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
1573
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, _fedify_vocab.CryptographicKey, {
1224
1574
  documentLoader,
1225
1575
  contextLoader,
1226
1576
  keyCache,
1227
1577
  tracerProvider
1228
- });
1578
+ }));
1229
1579
  if (fetchError != null) {
1230
- failure = keyFetchErrorResult(keyId, fetchError);
1580
+ setFailure(keyFetchErrorResult(keyId, fetchError));
1231
1581
  continue;
1232
1582
  }
1233
1583
  if (!key) {
1234
1584
  logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
1235
- failure = invalidSignatureResult(keyId);
1585
+ setFailure(invalidSignatureResult(keyId));
1236
1586
  continue;
1237
1587
  }
1238
1588
  let alg = sigInput.alg?.toLowerCase();
@@ -1244,12 +1594,13 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1244
1594
  }
1245
1595
  if (alg) span?.setAttribute("http_signatures.algorithm", alg);
1246
1596
  const algorithm = alg && rfc9421AlgorithmMap[alg];
1597
+ const candidateAlgorithm = algorithm ? alg : void 0;
1247
1598
  if (!algorithm) {
1248
1599
  logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
1249
1600
  algorithm: sigInput.alg,
1250
1601
  supported: Object.keys(rfc9421AlgorithmMap)
1251
1602
  });
1252
- failure = invalidSignatureResult(keyId);
1603
+ setFailure(invalidSignatureResult(keyId));
1253
1604
  continue;
1254
1605
  }
1255
1606
  let signatureBase;
@@ -1260,20 +1611,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1260
1611
  error,
1261
1612
  signatureInput: sigInput
1262
1613
  });
1263
- failure = invalidSignatureResult(keyId);
1614
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1264
1615
  continue;
1265
1616
  }
1266
1617
  const signatureBaseBytes = new TextEncoder().encode(signatureBase);
1267
1618
  span?.setAttribute("http_signatures.signature", (0, byte_encodings_hex.encodeHex)(sigBytes));
1268
1619
  try {
1269
- if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) return {
1270
- verified: true,
1271
- key,
1272
- signatureLabel: sigName
1273
- };
1274
- else if (cached) {
1620
+ if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
1621
+ metricsContext.algorithm = candidateAlgorithm;
1622
+ return {
1623
+ verified: true,
1624
+ key,
1625
+ signatureLabel: sigName
1626
+ };
1627
+ } else if (cached) {
1275
1628
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
1276
- return await verifyRequestDetailed(originalRequest, {
1629
+ return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
1277
1630
  documentLoader,
1278
1631
  contextLoader,
1279
1632
  timeWindow,
@@ -1282,14 +1635,16 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1282
1635
  get: () => Promise.resolve(void 0),
1283
1636
  set: async (keyId, key) => await keyCache?.set(keyId, key)
1284
1637
  },
1285
- spec: "rfc9421"
1638
+ spec: "rfc9421",
1639
+ meterProvider,
1640
+ tracerProvider
1286
1641
  });
1287
1642
  } else {
1288
1643
  logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
1289
1644
  keyId: sigInput.keyId,
1290
1645
  signatureBase
1291
1646
  });
1292
- failure = invalidSignatureResult(keyId);
1647
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1293
1648
  }
1294
1649
  } catch (error) {
1295
1650
  logger.debug("Error during signature verification: {error}", {
@@ -1297,9 +1652,10 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1297
1652
  keyId: sigInput.keyId,
1298
1653
  algorithm: sigInput.alg
1299
1654
  });
1300
- failure = invalidSignatureResult(keyId);
1655
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1301
1656
  }
1302
1657
  }
1658
+ metricsContext.algorithm = failureAlgorithm;
1303
1659
  return failure;
1304
1660
  }
1305
1661
  /**
@@ -1563,12 +1919,42 @@ Object.defineProperty(exports, "generateCryptoKeyPair", {
1563
1919
  return generateCryptoKeyPair;
1564
1920
  }
1565
1921
  });
1922
+ Object.defineProperty(exports, "getDurationMs", {
1923
+ enumerable: true,
1924
+ get: function() {
1925
+ return getDurationMs;
1926
+ }
1927
+ });
1928
+ Object.defineProperty(exports, "getFederationMetrics", {
1929
+ enumerable: true,
1930
+ get: function() {
1931
+ return getFederationMetrics;
1932
+ }
1933
+ });
1934
+ Object.defineProperty(exports, "getRemoteHost", {
1935
+ enumerable: true,
1936
+ get: function() {
1937
+ return getRemoteHost;
1938
+ }
1939
+ });
1566
1940
  Object.defineProperty(exports, "importJwk", {
1567
1941
  enumerable: true,
1568
1942
  get: function() {
1569
1943
  return importJwk;
1570
1944
  }
1571
1945
  });
1946
+ Object.defineProperty(exports, "isAbortError", {
1947
+ enumerable: true,
1948
+ get: function() {
1949
+ return isAbortError$1;
1950
+ }
1951
+ });
1952
+ Object.defineProperty(exports, "measureSignatureKeyFetch", {
1953
+ enumerable: true,
1954
+ get: function() {
1955
+ return measureSignatureKeyFetch;
1956
+ }
1957
+ });
1572
1958
  Object.defineProperty(exports, "name", {
1573
1959
  enumerable: true,
1574
1960
  get: function() {
@@ -1587,6 +1973,12 @@ Object.defineProperty(exports, "parseRfc9421SignatureInput", {
1587
1973
  return parseRfc9421SignatureInput;
1588
1974
  }
1589
1975
  });
1976
+ Object.defineProperty(exports, "recordOutboxEnqueue", {
1977
+ enumerable: true,
1978
+ get: function() {
1979
+ return recordOutboxEnqueue;
1980
+ }
1981
+ });
1590
1982
  Object.defineProperty(exports, "signRequest", {
1591
1983
  enumerable: true,
1592
1984
  get: function() {