@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.
- package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DQP-q39h.mjs} +27 -2
- package/dist/{builder-BkRRjxzb.mjs → builder-YlEusQth.mjs} +3 -3
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +1 -1
- package/dist/compat/public-audience.test.mjs +1 -1
- package/dist/compat/transformers.test.mjs +2 -2
- package/dist/{context-C0C_sRha.d.cts → context-Ch-ZLyTQ.d.cts} +1 -1
- package/dist/{context-Dqgt8saU.d.ts → context-cSUMk2da.d.ts} +1 -1
- package/dist/{deno-DBabeupC.mjs → deno-CF3jMgip.mjs} +1 -1
- package/dist/{docloader-DA5FzJOR.mjs → docloader-BENj6vQ4.mjs} +2 -2
- package/dist/federation/builder.test.mjs +3 -3
- package/dist/federation/collection.test.mjs +2 -2
- package/dist/federation/handler.test.mjs +8 -7
- package/dist/federation/idempotency.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/keycache.test.mjs +1 -1
- package/dist/federation/kv.test.mjs +2 -2
- package/dist/federation/middleware.test.mjs +10 -10
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +2 -2
- package/dist/federation/negotiation.test.mjs +2 -2
- package/dist/federation/router.test.mjs +2 -2
- package/dist/federation/send.test.mjs +11 -11
- package/dist/federation/webfinger.test.mjs +3 -3
- package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
- package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
- package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
- package/dist/{http-5G18W3NP.mjs → http-BmOZYc-8.mjs} +86 -37
- package/dist/{http-W2u_KBoQ.cjs → http-CKCgOPkX.cjs} +427 -35
- package/dist/{http-Dzy5c472.js → http-CpzZ9zsb.js} +393 -37
- package/dist/{http-BDZeS5om.d.ts → http-D6LP89UO.d.ts} +7 -1
- package/dist/{http-C87EWkO0.d.cts → http-D6aw3j2U.d.cts} +7 -1
- package/dist/{key-D9dUsyow.mjs → key-B4I8H5Lc.mjs} +1 -1
- package/dist/{kv-cache-BygrlQ1c.cjs → kv-cache-DY-XWOqM.cjs} +1 -1
- package/dist/{kv-cache-CBSgxEsZ.js → kv-cache-Wc5ezcVW.js} +1 -1
- package/dist/{ld-hbxDLO1k.mjs → ld-B5D5THhl.mjs} +60 -9
- package/dist/{send-BOwz4Hw5.mjs → metrics-ek3ilf6c.mjs} +53 -221
- package/dist/{middleware-vCF_cKAq.js → middleware-CuZbBw-N.js} +16 -269
- package/dist/{middleware-BXnhAGF9.mjs → middleware-DlcecZMq.mjs} +29 -23
- package/dist/{middleware-DZQsPMZb.mjs → middleware-EI7OU6BR.mjs} +1 -1
- package/dist/{middleware-Caj827xW.cjs → middleware-EqTYPG4F.cjs} +45 -298
- package/dist/{mod-DXY9JF28.d.cts → mod-B-Lin9Sy.d.ts} +25 -2
- package/dist/{mod-DHO9lk3D.d.ts → mod-BDhgfjP7.d.cts} +25 -2
- package/dist/{mod-B0rWmfW5.d.cts → mod-BR_BB0bh.d.cts} +1 -1
- package/dist/{mod-Dx3-hqyo.d.ts → mod-C6E8rkcz.d.ts} +1 -1
- package/dist/{mod-BhU_H1I_.d.ts → mod-DLrRb0dx.d.ts} +1 -1
- package/dist/{mod-CLPnQPsv.d.cts → mod-P9tE2WmM.d.cts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/client.test.mjs +2 -2
- package/dist/nodeinfo/handler.test.mjs +3 -3
- package/dist/nodeinfo/types.test.mjs +2 -2
- package/dist/otel/exporter.test.mjs +2 -2
- package/dist/{outgoing-jsonld-BgFLCJQ_.mjs → outgoing-jsonld-BNL8AC14.mjs} +1 -1
- package/dist/{owner-DwJe0BH9.mjs → owner-DO810N24.mjs} +2 -2
- package/dist/{proof-erpV_J_n.mjs → proof-BgfyWv7b.mjs} +25 -7
- package/dist/{proof-CZCaAURh.cjs → proof-DIoqrKnX.cjs} +78 -11
- package/dist/{proof-DMJJZnKd.js → proof-Vd8-1EWh.js} +78 -11
- package/dist/send-CAYXdUTk.mjs +225 -0
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/http.test.mjs +212 -6
- package/dist/sig/key.test.mjs +4 -4
- package/dist/sig/ld.test.mjs +138 -5
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +4 -4
- package/dist/sig/proof.test.mjs +167 -6
- package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BTEgfoJo.mjs} +2 -27
- package/dist/utils/docloader.test.mjs +5 -5
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
- /package/dist/{accept-CceiKpCy.mjs → accept-CgDcxvjV.mjs} +0 -0
- /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-BeTGV3wc.mjs} +0 -0
- /package/dist/{client-B_A6mfn3.mjs → client-Bneh_DYR.mjs} +0 -0
- /package/dist/{collection-CA3V5zyK.mjs → collection-Cc3DVAhE.mjs} +0 -0
- /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
- /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
- /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
- /package/dist/{keys-C3kae-6B.mjs → keys-CSYsOMFG.mjs} +0 -0
- /package/dist/{kv-x2IvBUyq.mjs → kv-QHE0oeM3.mjs} +0 -0
- /package/dist/{kv-cache-CiiNwT6W.mjs → kv-cache-DihufyAQ.mjs} +0 -0
- /package/dist/{public-audience-N3pyOx2p.mjs → public-audience-c9zmYKgA.mjs} +0 -0
- /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.
|
|
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
|
-
|
|
850
|
-
|
|
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)
|
|
1015
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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() {
|