@fedify/fedify 2.3.0-dev.1099 → 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-BkRRjxzb.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-DBabeupC.mjs → deno-CF3jMgip.mjs} +1 -1
  11. package/dist/{docloader-DA5FzJOR.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-5G18W3NP.mjs → http-BmOZYc-8.mjs} +86 -37
  33. package/dist/{http-W2u_KBoQ.cjs → http-CKCgOPkX.cjs} +427 -35
  34. package/dist/{http-Dzy5c472.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-D9dUsyow.mjs → key-B4I8H5Lc.mjs} +1 -1
  38. package/dist/{kv-cache-BygrlQ1c.cjs → kv-cache-DY-XWOqM.cjs} +1 -1
  39. package/dist/{kv-cache-CBSgxEsZ.js → kv-cache-Wc5ezcVW.js} +1 -1
  40. package/dist/{ld-hbxDLO1k.mjs → ld-B5D5THhl.mjs} +60 -9
  41. package/dist/{send-BOwz4Hw5.mjs → metrics-ek3ilf6c.mjs} +53 -221
  42. package/dist/{middleware-vCF_cKAq.js → middleware-CuZbBw-N.js} +16 -269
  43. package/dist/{middleware-BXnhAGF9.mjs → middleware-DlcecZMq.mjs} +29 -23
  44. package/dist/{middleware-DZQsPMZb.mjs → middleware-EI7OU6BR.mjs} +1 -1
  45. package/dist/{middleware-Caj827xW.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-DwJe0BH9.mjs → owner-DO810N24.mjs} +2 -2
  62. package/dist/{proof-erpV_J_n.mjs → proof-BgfyWv7b.mjs} +25 -7
  63. package/dist/{proof-CZCaAURh.cjs → proof-DIoqrKnX.cjs} +78 -11
  64. package/dist/{proof-DMJJZnKd.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 +6 -6
  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
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { getLogger } from "@logtape/logtape";
4
4
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
5
- import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
5
+ import { SpanKind, SpanStatusCode, metrics, trace } from "@opentelemetry/api";
6
6
  import { encodeHex } from "byte-encodings/hex";
7
7
  import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
8
8
  import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.3.0-dev.1099+fafcfa78";
13
+ var version = "2.3.0-dev.1114+15a3316d";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -151,6 +151,314 @@ function fulfillAcceptSignature(entry, localKeyId, localAlg) {
151
151
  };
152
152
  }
153
153
  //#endregion
154
+ //#region src/federation/metrics.ts
155
+ var FederationMetrics = class {
156
+ deliverySent;
157
+ deliveryPermanentFailure;
158
+ signatureVerificationFailure;
159
+ signatureVerificationDuration;
160
+ signatureKeyFetchDuration;
161
+ deliveryDuration;
162
+ inboxProcessingDuration;
163
+ httpServerRequestCount;
164
+ httpServerRequestDuration;
165
+ queueTaskEnqueued;
166
+ queueTaskStarted;
167
+ queueTaskCompleted;
168
+ queueTaskFailed;
169
+ queueTaskDuration;
170
+ queueTaskInFlight;
171
+ constructor(meterProvider) {
172
+ const meter = meterProvider.getMeter(name, version);
173
+ this.deliverySent = meter.createCounter("activitypub.delivery.sent", {
174
+ description: "ActivityPub delivery attempts.",
175
+ unit: "{attempt}"
176
+ });
177
+ this.deliveryPermanentFailure = meter.createCounter("activitypub.delivery.permanent_failure", {
178
+ description: "ActivityPub deliveries abandoned as permanent failures.",
179
+ unit: "{failure}"
180
+ });
181
+ this.signatureVerificationFailure = meter.createCounter("activitypub.signature.verification_failure", {
182
+ description: "ActivityPub signature verification failures.",
183
+ unit: "{failure}"
184
+ });
185
+ this.signatureVerificationDuration = meter.createHistogram("activitypub.signature.verification.duration", {
186
+ description: "Duration of ActivityPub signature verification, including local key lookup and remote key fetches.",
187
+ unit: "ms"
188
+ });
189
+ this.signatureKeyFetchDuration = meter.createHistogram("activitypub.signature.key_fetch.duration", {
190
+ description: "Duration of public key lookup performed during ActivityPub signature verification.",
191
+ unit: "ms"
192
+ });
193
+ this.deliveryDuration = meter.createHistogram("activitypub.delivery.duration", {
194
+ description: "Duration of ActivityPub delivery attempts.",
195
+ unit: "ms"
196
+ });
197
+ this.inboxProcessingDuration = meter.createHistogram("activitypub.inbox.processing_duration", {
198
+ description: "Duration of ActivityPub inbox listener processing.",
199
+ unit: "ms"
200
+ });
201
+ this.httpServerRequestCount = meter.createCounter("fedify.http.server.request.count", {
202
+ description: "HTTP requests handled by Federation.fetch().",
203
+ unit: "{request}"
204
+ });
205
+ this.httpServerRequestDuration = meter.createHistogram("fedify.http.server.request.duration", {
206
+ description: "Duration of HTTP requests handled by Federation.fetch().",
207
+ unit: "ms",
208
+ advice: { explicitBucketBoundaries: [
209
+ 5,
210
+ 10,
211
+ 25,
212
+ 50,
213
+ 75,
214
+ 100,
215
+ 250,
216
+ 500,
217
+ 750,
218
+ 1e3,
219
+ 2500,
220
+ 5e3,
221
+ 7500,
222
+ 1e4
223
+ ] }
224
+ });
225
+ this.queueTaskEnqueued = meter.createCounter("fedify.queue.task.enqueued", {
226
+ description: "Tasks Fedify enqueued for inbox, outbox, or fanout work.",
227
+ unit: "{task}"
228
+ });
229
+ this.queueTaskStarted = meter.createCounter("fedify.queue.task.started", {
230
+ description: "Tasks Fedify began processing as a queue worker.",
231
+ unit: "{task}"
232
+ });
233
+ this.queueTaskCompleted = meter.createCounter("fedify.queue.task.completed", {
234
+ description: "Queue tasks Fedify finished processing without throwing.",
235
+ unit: "{task}"
236
+ });
237
+ this.queueTaskFailed = meter.createCounter("fedify.queue.task.failed", {
238
+ description: "Queue tasks Fedify abandoned because processing threw.",
239
+ unit: "{task}"
240
+ });
241
+ this.queueTaskDuration = meter.createHistogram("fedify.queue.task.duration", {
242
+ description: "Duration of queue task processing in Fedify workers.",
243
+ unit: "ms",
244
+ advice: { explicitBucketBoundaries: [
245
+ 5,
246
+ 10,
247
+ 25,
248
+ 50,
249
+ 75,
250
+ 100,
251
+ 250,
252
+ 500,
253
+ 750,
254
+ 1e3,
255
+ 2500,
256
+ 5e3,
257
+ 7500,
258
+ 1e4
259
+ ] }
260
+ });
261
+ this.queueTaskInFlight = meter.createUpDownCounter("fedify.queue.task.in_flight", {
262
+ description: "Queue tasks currently being processed in this Fedify process.",
263
+ unit: "{task}"
264
+ });
265
+ }
266
+ recordDelivery(inbox, durationMs, success, activityType) {
267
+ const deliveryAttributes = {
268
+ "activitypub.remote.host": getRemoteHost(inbox),
269
+ "activitypub.delivery.success": success
270
+ };
271
+ if (activityType != null) deliveryAttributes["activitypub.activity.type"] = activityType;
272
+ this.deliverySent.add(1, deliveryAttributes);
273
+ this.deliveryDuration.record(durationMs, deliveryAttributes);
274
+ }
275
+ recordPermanentFailure(inbox, statusCode) {
276
+ this.deliveryPermanentFailure.add(1, {
277
+ "activitypub.remote.host": getRemoteHost(inbox),
278
+ "http.response.status_code": statusCode
279
+ });
280
+ }
281
+ recordSignatureVerificationFailure(reason, remoteHost) {
282
+ const attributes = { "activitypub.verification.failure_reason": reason };
283
+ if (remoteHost != null) attributes["activitypub.remote.host"] = remoteHost;
284
+ this.signatureVerificationFailure.add(1, attributes);
285
+ }
286
+ recordSignatureVerificationDuration(durationMs, kind, result, extra = {}) {
287
+ const attributes = {
288
+ "activitypub.signature.kind": kind,
289
+ "activitypub.signature.result": result
290
+ };
291
+ if (extra.algorithm != null) attributes["http_signatures.algorithm"] = extra.algorithm;
292
+ if (extra.failureReason != null) attributes["http_signatures.failure_reason"] = extra.failureReason;
293
+ if (extra.ldType != null) attributes["ld_signatures.type"] = extra.ldType;
294
+ if (extra.cryptosuite != null) attributes["object_integrity_proofs.cryptosuite"] = extra.cryptosuite;
295
+ this.signatureVerificationDuration.record(durationMs, attributes);
296
+ }
297
+ recordSignatureKeyFetchDuration(durationMs, kind, result) {
298
+ this.signatureKeyFetchDuration.record(durationMs, {
299
+ "activitypub.signature.kind": kind,
300
+ "activitypub.signature.key_fetch.result": result
301
+ });
302
+ }
303
+ recordInboxProcessingDuration(activityType, durationMs) {
304
+ this.inboxProcessingDuration.record(durationMs, { "activitypub.activity.type": activityType });
305
+ }
306
+ recordHttpServerRequest(method, endpoint, durationMs, options = {}) {
307
+ const attributes = {
308
+ "http.request.method": normalizeHttpMethod(method),
309
+ "fedify.endpoint": endpoint
310
+ };
311
+ if (options.statusCode != null) attributes["http.response.status_code"] = options.statusCode;
312
+ if (options.routeTemplate != null) attributes["fedify.route.template"] = options.routeTemplate;
313
+ this.httpServerRequestCount.add(1, attributes);
314
+ this.httpServerRequestDuration.record(durationMs, attributes);
315
+ }
316
+ recordQueueTaskEnqueued(common, attempt) {
317
+ const attributes = buildQueueTaskAttributes(common);
318
+ attributes["fedify.queue.task.attempt"] = attempt;
319
+ this.queueTaskEnqueued.add(1, attributes);
320
+ }
321
+ recordQueueTaskStarted(common) {
322
+ this.queueTaskStarted.add(1, buildQueueTaskAttributes(common));
323
+ }
324
+ incrementQueueTaskInFlight(common) {
325
+ this.queueTaskInFlight.add(1, buildQueueTaskInFlightAttributes(common));
326
+ }
327
+ decrementQueueTaskInFlight(common) {
328
+ this.queueTaskInFlight.add(-1, buildQueueTaskInFlightAttributes(common));
329
+ }
330
+ recordQueueTaskOutcome(common, result, durationMs) {
331
+ const attributes = buildQueueTaskAttributes(common);
332
+ attributes["fedify.queue.task.result"] = result;
333
+ if (result === "completed") this.queueTaskCompleted.add(1, attributes);
334
+ else if (result === "failed") this.queueTaskFailed.add(1, attributes);
335
+ this.queueTaskDuration.record(durationMs, attributes);
336
+ }
337
+ };
338
+ function buildQueueTaskAttributes(common) {
339
+ const attributes = { "fedify.queue.role": common.role };
340
+ const backend = getQueueBackend(common.queue);
341
+ if (backend != null) attributes["fedify.queue.backend"] = backend;
342
+ const nativeRetrial = common.queue?.nativeRetrial;
343
+ if (typeof nativeRetrial === "boolean") attributes["fedify.queue.native_retrial"] = nativeRetrial;
344
+ if (common.activityType != null) attributes["activitypub.activity.type"] = common.activityType;
345
+ return attributes;
346
+ }
347
+ function buildQueueTaskInFlightAttributes(common) {
348
+ return buildQueueTaskAttributes({
349
+ role: common.role,
350
+ queue: common.queue
351
+ });
352
+ }
353
+ /**
354
+ * Returns the constructor name of the given message queue, when it is a
355
+ * meaningful identifier. Used as a best-effort `fedify.queue.backend`
356
+ * attribute on queue task metrics; returns `undefined` for plain object
357
+ * literals (whose constructor is `Object`) so the attribute does not appear
358
+ * with a non-informative value.
359
+ * @since 2.3.0
360
+ */
361
+ function getQueueBackend(queue) {
362
+ const name = queue?.constructor?.name;
363
+ if (name == null || name === "" || name === "Object") return void 0;
364
+ return name;
365
+ }
366
+ /**
367
+ * Records `fedify.queue.task.enqueued` for an outgoing outbox enqueue.
368
+ *
369
+ * Both `Context.sendActivity()` and `OutboxContext.forwardActivity()` enqueue
370
+ * outbox messages with the same metric attributes (role, queue, activity
371
+ * type, attempt), so they share this helper rather than each defining a local
372
+ * closure.
373
+ * @since 2.3.0
374
+ */
375
+ function recordOutboxEnqueue(meterProvider, outboxQueue, message) {
376
+ getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
377
+ role: "outbox",
378
+ queue: outboxQueue,
379
+ activityType: message.activityType
380
+ }, message.attempt);
381
+ }
382
+ /**
383
+ * Times an awaited public key fetch and records exactly one
384
+ * `activitypub.signature.key_fetch.duration` measurement, classifying the
385
+ * outcome as `hit`, `fetched`, or `error` based on the `cached` flag and
386
+ * whether the returned key is non-null. Errors thrown by the fetch are
387
+ * reported as `error` and rethrown, so verifier behavior is unchanged.
388
+ *
389
+ * Shared by the three signature verifiers (HTTP, Linked Data, Object
390
+ * Integrity Proofs); the only per-call variation is the
391
+ * `activitypub.signature.kind` attribute value.
392
+ * @since 2.3.0
393
+ */
394
+ async function measureSignatureKeyFetch(meterProvider, kind, fetch) {
395
+ const start = performance.now();
396
+ try {
397
+ const result = await fetch();
398
+ getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, result.key != null ? result.cached ? "hit" : "fetched" : "error");
399
+ return result;
400
+ } catch (error) {
401
+ getFederationMetrics(meterProvider).recordSignatureKeyFetchDuration(getDurationMs(start), kind, "error");
402
+ throw error;
403
+ }
404
+ }
405
+ /**
406
+ * Whether the given thrown value is an `AbortError`.
407
+ *
408
+ * `processQueuedTask` distinguishes aborted tasks (recorded as
409
+ * `fedify.queue.task.result=aborted`) from other failures so that backend
410
+ * shutdown signals do not inflate the `fedify.queue.task.failed` counter.
411
+ * @since 2.3.0
412
+ */
413
+ function isAbortError$1(error) {
414
+ if (error == null || typeof error !== "object") return false;
415
+ const name = error.name;
416
+ return typeof name === "string" && name === "AbortError";
417
+ }
418
+ const KNOWN_HTTP_METHODS = new Set([
419
+ "CONNECT",
420
+ "DELETE",
421
+ "GET",
422
+ "HEAD",
423
+ "OPTIONS",
424
+ "PATCH",
425
+ "POST",
426
+ "PUT",
427
+ "QUERY",
428
+ "TRACE"
429
+ ]);
430
+ function normalizeHttpMethod(method) {
431
+ const upper = method.toUpperCase();
432
+ return KNOWN_HTTP_METHODS.has(upper) ? upper : "_OTHER";
433
+ }
434
+ const federationMetrics = /* @__PURE__ */ new WeakMap();
435
+ /**
436
+ * Gets the cached Fedify metric instruments for a meter provider.
437
+ * @since 2.3.0
438
+ */
439
+ function getFederationMetrics(meterProvider = metrics.getMeterProvider()) {
440
+ let instruments = federationMetrics.get(meterProvider);
441
+ if (instruments == null) {
442
+ instruments = new FederationMetrics(meterProvider);
443
+ federationMetrics.set(meterProvider, instruments);
444
+ }
445
+ return instruments;
446
+ }
447
+ /**
448
+ * Gets the bounded remote host attribute value for a URL.
449
+ * @since 2.3.0
450
+ */
451
+ function getRemoteHost(url) {
452
+ return url.hostname;
453
+ }
454
+ /**
455
+ * Gets an elapsed duration in milliseconds from a `performance.now()` value.
456
+ * @since 2.3.0
457
+ */
458
+ function getDurationMs(start) {
459
+ return Math.max(0, performance.now() - start);
460
+ }
461
+ //#endregion
154
462
  //#region src/sig/key.ts
155
463
  /**
156
464
  * Checks if the given key is valid and supported. No-op if the key is valid,
@@ -799,6 +1107,27 @@ function parseKeyId(value) {
799
1107
  function getKeyFetchErrorName(error) {
800
1108
  return error.name || error.constructor.name || "Error";
801
1109
  }
1110
+ /**
1111
+ * Known draft-cavage `algorithm` parameter values, used to keep the
1112
+ * `http_signatures.algorithm` metric attribute on a bounded set. The header
1113
+ * field is attacker-controlled and not used to select the verification
1114
+ * algorithm, so unknown values are dropped from the metric to prevent
1115
+ * cardinality blow-up.
1116
+ */
1117
+ const DRAFT_KNOWN_ALGORITHMS = new Set([
1118
+ "ecdsa-sha256",
1119
+ "ecdsa-sha384",
1120
+ "ecdsa-sha512",
1121
+ "ed25519",
1122
+ "hs2019",
1123
+ "rsa-sha1",
1124
+ "rsa-sha256",
1125
+ "rsa-sha512"
1126
+ ]);
1127
+ function classifyHttpVerifyResult(result) {
1128
+ if (result.verified) return "verified";
1129
+ return result.reason.type === "noSignature" ? "missing" : "rejected";
1130
+ }
802
1131
  function recordVerificationResult(span, result) {
803
1132
  span.setAttribute("http_signatures.verified", result.verified);
804
1133
  if (result.verified === true) return;
@@ -842,27 +1171,37 @@ async function verifyRequestDetailed(request, options = {}) {
842
1171
  span.setAttribute(ATTR_URL_FULL, request.url);
843
1172
  for (const [name, value] of request.headers) span.setAttribute(ATTR_HTTP_REQUEST_HEADER(name), value);
844
1173
  }
1174
+ const start = performance.now();
1175
+ const metricsContext = {};
1176
+ let result;
1177
+ let threw = false;
845
1178
  try {
846
1179
  let spec = options.spec;
847
1180
  if (spec == null) spec = request.headers.has("Signature-Input") ? "rfc9421" : "draft-cavage-http-signatures-12";
848
- let result;
849
- if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, options);
850
- else result = await verifyRequestDraft(request, span, options);
1181
+ if (spec === "rfc9421") result = await verifyRequestRfc9421(request, span, metricsContext, options);
1182
+ else result = await verifyRequestDraft(request, span, metricsContext, options);
851
1183
  recordVerificationResult(span, result);
852
1184
  if (!result.verified) span.setStatus({ code: SpanStatusCode.ERROR });
853
1185
  return result;
854
1186
  } catch (error) {
1187
+ threw = true;
855
1188
  span.setStatus({
856
1189
  code: SpanStatusCode.ERROR,
857
1190
  message: String(error)
858
1191
  });
859
1192
  throw error;
860
1193
  } finally {
1194
+ const classified = threw ? "error" : classifyHttpVerifyResult(result);
1195
+ const failureReason = result != null && !result.verified && result.reason.type !== "noSignature" ? result.reason.type : void 0;
1196
+ getFederationMetrics(options.meterProvider).recordSignatureVerificationDuration(getDurationMs(start), "http", classified, {
1197
+ algorithm: metricsContext.algorithm,
1198
+ failureReason
1199
+ });
861
1200
  span.end();
862
1201
  }
863
1202
  });
864
1203
  }
865
- async function verifyRequestDraft(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
1204
+ async function verifyRequestDraft(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
866
1205
  const logger = getLogger([
867
1206
  "fedify",
868
1207
  "sig",
@@ -1010,13 +1349,17 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1010
1349
  const keyIdUrl = parseKeyId(keyId);
1011
1350
  if (keyIdUrl == null) return invalidSignatureResult(null);
1012
1351
  span?.setAttribute("http_signatures.key_id", keyId);
1013
- if ("algorithm" in sigValues) span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
1014
- const { key, cached, fetchError } = await fetchKeyDetailed(keyIdUrl, CryptographicKey, {
1352
+ if ("algorithm" in sigValues) {
1353
+ span?.setAttribute("http_signatures.algorithm", sigValues.algorithm);
1354
+ const normalizedAlgorithm = sigValues.algorithm.toLowerCase();
1355
+ if (DRAFT_KNOWN_ALGORITHMS.has(normalizedAlgorithm)) metricsContext.algorithm = normalizedAlgorithm;
1356
+ }
1357
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyIdUrl, CryptographicKey, {
1015
1358
  documentLoader,
1016
1359
  contextLoader,
1017
1360
  keyCache,
1018
1361
  tracerProvider
1019
- });
1362
+ }));
1020
1363
  if (fetchError != null) return keyFetchErrorResult(keyIdUrl, fetchError);
1021
1364
  if (key == null) return invalidSignatureResult(keyIdUrl);
1022
1365
  const headerNames = headers.split(/\s+/g);
@@ -1038,7 +1381,7 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1038
1381
  signature,
1039
1382
  message
1040
1383
  });
1041
- return await verifyRequestDetailed(originalRequest, {
1384
+ return await verifyRequestDraft(originalRequest, span, metricsContext, {
1042
1385
  documentLoader,
1043
1386
  contextLoader,
1044
1387
  timeWindow,
@@ -1046,7 +1389,9 @@ async function verifyRequestDraft(request, span, { documentLoader, contextLoader
1046
1389
  keyCache: {
1047
1390
  get: () => Promise.resolve(void 0),
1048
1391
  set: async (keyId, key) => await keyCache?.set(keyId, key)
1049
- }
1392
+ },
1393
+ meterProvider,
1394
+ tracerProvider
1050
1395
  });
1051
1396
  }
1052
1397
  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}", {
@@ -1122,7 +1467,7 @@ async function verifyRfc9421ContentDigest(digestHeader, body) {
1122
1467
  }
1123
1468
  return false;
1124
1469
  }
1125
- async function verifyRequestRfc9421(request, span, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, tracerProvider } = {}) {
1470
+ async function verifyRequestRfc9421(request, span, metricsContext, { documentLoader, contextLoader, timeWindow, currentTime, keyCache, meterProvider, tracerProvider } = {}) {
1126
1471
  const logger = getLogger([
1127
1472
  "fedify",
1128
1473
  "sig",
@@ -1156,9 +1501,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1156
1501
  return invalidSignatureResult(null);
1157
1502
  }
1158
1503
  let failure = noSignatureResult();
1504
+ let failureAlgorithm;
1505
+ const setFailure = (result, algorithm) => {
1506
+ failure = result;
1507
+ failureAlgorithm = algorithm;
1508
+ };
1159
1509
  for (const sigName of signatureNames) {
1160
1510
  if (!signatures[sigName]) {
1161
- failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
1511
+ setFailure(invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId)));
1162
1512
  continue;
1163
1513
  }
1164
1514
  const sigInput = signatureInputs[sigName];
@@ -1169,7 +1519,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1169
1519
  signatureName: sigName,
1170
1520
  signatureInput: signatureInputHeader
1171
1521
  });
1172
- failure = invalidSignatureResult(null);
1522
+ setFailure(invalidSignatureResult(null));
1173
1523
  continue;
1174
1524
  }
1175
1525
  if (!sigInput.created) {
@@ -1177,7 +1527,7 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1177
1527
  signatureName: sigName,
1178
1528
  signatureInput: signatureInputHeader
1179
1529
  });
1180
- failure = invalidSignatureResult(keyId);
1530
+ setFailure(invalidSignatureResult(keyId));
1181
1531
  continue;
1182
1532
  }
1183
1533
  const signatureCreated = Temporal.Instant.fromEpochMilliseconds(sigInput.created * 1e3);
@@ -1189,14 +1539,14 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1189
1539
  created: signatureCreated.toString(),
1190
1540
  now: now.toString()
1191
1541
  });
1192
- failure = invalidSignatureResult(keyId);
1542
+ setFailure(invalidSignatureResult(keyId));
1193
1543
  continue;
1194
1544
  } else if (Temporal.Instant.compare(signatureCreated, now.subtract(tw)) < 0) {
1195
1545
  logger.debug("Failed to verify; signature created time is too far in the past.", {
1196
1546
  created: signatureCreated.toString(),
1197
1547
  now: now.toString()
1198
1548
  });
1199
- failure = invalidSignatureResult(keyId);
1549
+ setFailure(invalidSignatureResult(keyId));
1200
1550
  continue;
1201
1551
  }
1202
1552
  }
@@ -1204,34 +1554,34 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1204
1554
  const contentDigestHeader = request.headers.get("Content-Digest");
1205
1555
  if (!contentDigestHeader) {
1206
1556
  logger.debug("Failed to verify; Content-Digest header required but not found.", { components: sigInput.components });
1207
- failure = invalidSignatureResult(keyId);
1557
+ setFailure(invalidSignatureResult(keyId));
1208
1558
  continue;
1209
1559
  }
1210
1560
  if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
1211
1561
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1212
- failure = invalidSignatureResult(keyId);
1562
+ setFailure(invalidSignatureResult(keyId));
1213
1563
  continue;
1214
1564
  }
1215
1565
  }
1216
1566
  span?.setAttribute("http_signatures.key_id", sigInput.keyId);
1217
1567
  span?.setAttribute("http_signatures.created", sigInput.created.toString());
1218
1568
  if (keyId == null) {
1219
- failure = invalidSignatureResult(null);
1569
+ setFailure(invalidSignatureResult(null));
1220
1570
  continue;
1221
1571
  }
1222
- const { key, cached, fetchError } = await fetchKeyDetailed(keyId, CryptographicKey, {
1572
+ const { key, cached, fetchError } = await measureSignatureKeyFetch(meterProvider, "http", () => fetchKeyDetailed(keyId, CryptographicKey, {
1223
1573
  documentLoader,
1224
1574
  contextLoader,
1225
1575
  keyCache,
1226
1576
  tracerProvider
1227
- });
1577
+ }));
1228
1578
  if (fetchError != null) {
1229
- failure = keyFetchErrorResult(keyId, fetchError);
1579
+ setFailure(keyFetchErrorResult(keyId, fetchError));
1230
1580
  continue;
1231
1581
  }
1232
1582
  if (!key) {
1233
1583
  logger.debug("Failed to fetch key: {keyId}", { keyId: sigInput.keyId });
1234
- failure = invalidSignatureResult(keyId);
1584
+ setFailure(invalidSignatureResult(keyId));
1235
1585
  continue;
1236
1586
  }
1237
1587
  let alg = sigInput.alg?.toLowerCase();
@@ -1243,12 +1593,13 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1243
1593
  }
1244
1594
  if (alg) span?.setAttribute("http_signatures.algorithm", alg);
1245
1595
  const algorithm = alg && rfc9421AlgorithmMap[alg];
1596
+ const candidateAlgorithm = algorithm ? alg : void 0;
1246
1597
  if (!algorithm) {
1247
1598
  logger.debug("Failed to verify; unsupported algorithm: {algorithm}", {
1248
1599
  algorithm: sigInput.alg,
1249
1600
  supported: Object.keys(rfc9421AlgorithmMap)
1250
1601
  });
1251
- failure = invalidSignatureResult(keyId);
1602
+ setFailure(invalidSignatureResult(keyId));
1252
1603
  continue;
1253
1604
  }
1254
1605
  let signatureBase;
@@ -1259,20 +1610,22 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1259
1610
  error,
1260
1611
  signatureInput: sigInput
1261
1612
  });
1262
- failure = invalidSignatureResult(keyId);
1613
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1263
1614
  continue;
1264
1615
  }
1265
1616
  const signatureBaseBytes = new TextEncoder().encode(signatureBase);
1266
1617
  span?.setAttribute("http_signatures.signature", encodeHex(sigBytes));
1267
1618
  try {
1268
- if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) return {
1269
- verified: true,
1270
- key,
1271
- signatureLabel: sigName
1272
- };
1273
- else if (cached) {
1619
+ if (await crypto.subtle.verify(algorithm, key.publicKey, sigBytes.slice(), signatureBaseBytes)) {
1620
+ metricsContext.algorithm = candidateAlgorithm;
1621
+ return {
1622
+ verified: true,
1623
+ key,
1624
+ signatureLabel: sigName
1625
+ };
1626
+ } else if (cached) {
1274
1627
  logger.debug("Failed to verify with cached key {keyId}; retrying with fresh key...", { keyId: sigInput.keyId });
1275
- return await verifyRequestDetailed(originalRequest, {
1628
+ return await verifyRequestRfc9421(originalRequest, span, metricsContext, {
1276
1629
  documentLoader,
1277
1630
  contextLoader,
1278
1631
  timeWindow,
@@ -1281,14 +1634,16 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1281
1634
  get: () => Promise.resolve(void 0),
1282
1635
  set: async (keyId, key) => await keyCache?.set(keyId, key)
1283
1636
  },
1284
- spec: "rfc9421"
1637
+ spec: "rfc9421",
1638
+ meterProvider,
1639
+ tracerProvider
1285
1640
  });
1286
1641
  } else {
1287
1642
  logger.debug("Failed to verify signature with fetched key {keyId}; signature invalid.", {
1288
1643
  keyId: sigInput.keyId,
1289
1644
  signatureBase
1290
1645
  });
1291
- failure = invalidSignatureResult(keyId);
1646
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1292
1647
  }
1293
1648
  } catch (error) {
1294
1649
  logger.debug("Error during signature verification: {error}", {
@@ -1296,9 +1651,10 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1296
1651
  keyId: sigInput.keyId,
1297
1652
  algorithm: sigInput.alg
1298
1653
  });
1299
- failure = invalidSignatureResult(keyId);
1654
+ setFailure(invalidSignatureResult(keyId), candidateAlgorithm);
1300
1655
  }
1301
1656
  }
1657
+ metricsContext.algorithm = failureAlgorithm;
1302
1658
  return failure;
1303
1659
  }
1304
1660
  /**
@@ -1520,4 +1876,4 @@ function timingSafeEqual(a, b) {
1520
1876
  return result === 0;
1521
1877
  }
1522
1878
  //#endregion
1523
- export { version as _, verifyRequestDetailed as a, fetchKeyDetailed as c, validateCryptoKey as d, formatAcceptSignature as f, name as g, validateAcceptSignature as h, verifyRequest as i, generateCryptoKeyPair as l, parseAcceptSignature as m, parseRfc9421SignatureInput as n, exportJwk as o, fulfillAcceptSignature as p, signRequest as r, fetchKey as s, doubleKnock as t, importJwk as u };
1879
+ export { version as C, name as S, recordOutboxEnqueue as _, verifyRequestDetailed as a, parseAcceptSignature as b, fetchKeyDetailed as c, validateCryptoKey as d, getDurationMs as f, measureSignatureKeyFetch as g, isAbortError$1 as h, verifyRequest as i, generateCryptoKeyPair as l, getRemoteHost as m, parseRfc9421SignatureInput as n, exportJwk as o, getFederationMetrics as p, signRequest as r, fetchKey as s, doubleKnock as t, importJwk as u, formatAcceptSignature as v, validateAcceptSignature as x, fulfillAcceptSignature as y };
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { CryptographicKey, Multikey } from "@fedify/vocab";
3
- import { TracerProvider } from "@opentelemetry/api";
3
+ import { MeterProvider, TracerProvider } from "@opentelemetry/api";
4
4
  import { DocumentLoader } from "@fedify/vocab-runtime";
5
5
 
6
6
  //#region src/sig/key.d.ts
@@ -464,6 +464,12 @@ interface VerifyRequestOptions {
464
464
  * @since 1.3.0
465
465
  */
466
466
  tracerProvider?: TracerProvider;
467
+ /**
468
+ * The OpenTelemetry meter provider. If omitted, the global meter provider
469
+ * is used.
470
+ * @since 2.3.0
471
+ */
472
+ meterProvider?: MeterProvider;
467
473
  }
468
474
  /**
469
475
  * The reason why {@link verifyRequestDetailed} could not verify a request.
@@ -1,7 +1,7 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { CryptographicKey, Multikey } from "@fedify/vocab";
3
3
  import { DocumentLoader } from "@fedify/vocab-runtime";
4
- import { TracerProvider } from "@opentelemetry/api";
4
+ import { MeterProvider, TracerProvider } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/sig/key.d.ts
7
7
  /**
@@ -464,6 +464,12 @@ interface VerifyRequestOptions {
464
464
  * @since 1.3.0
465
465
  */
466
466
  tracerProvider?: TracerProvider;
467
+ /**
468
+ * The OpenTelemetry meter provider. If omitted, the global meter provider
469
+ * is used.
470
+ * @since 2.3.0
471
+ */
472
+ meterProvider?: MeterProvider;
467
473
  }
468
474
  /**
469
475
  * The reason why {@link verifyRequestDetailed} could not verify a request.
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-DBabeupC.mjs";
4
+ import { n as version, t as name } from "./deno-CF3jMgip.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-W2u_KBoQ.cjs");
4
+ const require_http = require("./http-CKCgOPkX.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");