@fedify/fedify 2.4.2 → 2.4.3

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 (81) hide show
  1. package/dist/{builder-9ovCSqLp.mjs → builder-CztvQYX0.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-G2J_bU0u.mjs → compound-proof-rcvB6i5j.mjs} +2 -2
  4. package/dist/{deno-BZ4z-lJJ.mjs → deno-BEgaR4dC.mjs} +1 -1
  5. package/dist/{docloader-DUXXI-7G.mjs → docloader-Wf9RroxL.mjs} +4 -2
  6. package/dist/federation/builder.test.mjs +1 -1
  7. package/dist/federation/compound-fanout.test.mjs +3 -3
  8. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  9. package/dist/federation/handler.test.mjs +258 -5
  10. package/dist/federation/hashlink-media.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/inbox-report.test.mjs +5 -5
  13. package/dist/federation/inbox.test.mjs +43 -0
  14. package/dist/federation/metrics.test.mjs +1 -1
  15. package/dist/federation/middleware.test.mjs +172 -10
  16. package/dist/federation/mod.cjs +1 -1
  17. package/dist/federation/mod.js +1 -1
  18. package/dist/federation/outgoing-proof.test.mjs +4 -4
  19. package/dist/federation/portable-actor.test.mjs +3 -3
  20. package/dist/federation/portable-chain.test.mjs +3 -3
  21. package/dist/federation/portable-collection.test.mjs +4 -4
  22. package/dist/federation/portable-delivery.test.mjs +2 -2
  23. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  24. package/dist/federation/portable-inbox.test.mjs +4 -4
  25. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  26. package/dist/federation/portable-route-activity.test.mjs +2 -2
  27. package/dist/federation/portable.test.mjs +3 -3
  28. package/dist/federation/send.test.mjs +3 -3
  29. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  30. package/dist/federation/tasks/tasks.test.mjs +2 -2
  31. package/dist/federation/temporal.test.mjs +1 -1
  32. package/dist/federation/webfinger.test.mjs +1 -1
  33. package/dist/{federation-DpT8bDM6.cjs → federation-O8HwbuUv.cjs} +121 -20
  34. package/dist/{http-Qq7jR2rC.js → http-B5pRXkmQ.js} +1 -1
  35. package/dist/{http-x1satM4y.cjs → http-BFC8b7mH.cjs} +1 -1
  36. package/dist/{http-B5w_qcIg.mjs → http-CQcqhcga.mjs} +4 -4
  37. package/dist/inbox-CsErRN6L.mjs +188 -0
  38. package/dist/{kv-cache-BgQMAKIg.mjs → kv-cache-4oZXC3pf.mjs} +1 -1
  39. package/dist/{kv-cache-B0SZfzFb.js → kv-cache-Bih-8AoZ.js} +3 -1
  40. package/dist/{kv-cache-D4throaQ.cjs → kv-cache-JOKAStz2.cjs} +3 -1
  41. package/dist/{ld-Dkkyk65e.mjs → ld-Qk4WqslS.mjs} +2 -2
  42. package/dist/{metrics-CRv6_IBg.mjs → metrics-DAhJVtgg.mjs} +1 -1
  43. package/dist/{middleware-C-swet0z.mjs → middleware-B582s7UO.mjs} +103 -179
  44. package/dist/{middleware-DFtNaBcH.mjs → middleware-C76AHpbO.mjs} +1 -1
  45. package/dist/{middleware-BqefXH6B.js → middleware-KeANQWIR.js} +121 -20
  46. package/dist/mod.cjs +4 -4
  47. package/dist/mod.js +4 -4
  48. package/dist/nodeinfo/handler.test.mjs +1 -1
  49. package/dist/{owner-BHUhedVc.mjs → owner-CcyHGdSK.mjs} +3 -3
  50. package/dist/{portable-collection-DONEPTXV.mjs → portable-collection-0RzCsflU.mjs} +2 -2
  51. package/dist/{portable-collection-DL-1Wy1s.cjs → portable-collection-BHZq0F_c.cjs} +1 -1
  52. package/dist/{portable-collection-CfIc190c.js → portable-collection-Chb6A43p.js} +1 -1
  53. package/dist/{portable-key-Cdw3vfl-.mjs → portable-key-BY6G_X28.mjs} +1 -1
  54. package/dist/{proof-DuSE1ABD.mjs → proof-nIOXkRme.mjs} +3 -3
  55. package/dist/{send-D9oTjA6A.mjs → send-C2r9whkz.mjs} +3 -3
  56. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  57. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  58. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  59. package/dist/sig/compound-proof.test.mjs +2 -2
  60. package/dist/sig/http.test.mjs +2 -2
  61. package/dist/sig/key.test.mjs +1 -1
  62. package/dist/sig/ld.test.mjs +1 -1
  63. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  64. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  65. package/dist/sig/mod.cjs +2 -2
  66. package/dist/sig/mod.js +2 -2
  67. package/dist/sig/owner.test.mjs +1 -1
  68. package/dist/sig/portable-collection.test.mjs +1 -1
  69. package/dist/sig/portable-interop.test.mjs +3 -3
  70. package/dist/sig/portable-key.test.mjs +5 -5
  71. package/dist/sig/proof.test.mjs +3 -3
  72. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  73. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  74. package/dist/sig/verification.test.mjs +3 -3
  75. package/dist/{temporal-iyAxBq0I.mjs → temporal-D5tHfTGO.mjs} +1 -1
  76. package/dist/testing/kv.test.mjs +1 -1
  77. package/dist/utils/docloader.test.mjs +112 -37
  78. package/dist/utils/kv-cache.test.mjs +1 -1
  79. package/dist/utils/mod.cjs +1 -1
  80. package/dist/utils/mod.js +1 -1
  81. package/package.json +7 -7
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-x1satM4y.cjs");
4
+ const require_http = require("./http-BFC8b7mH.cjs");
5
5
  const require_types = require("./types-poIao40f.cjs");
6
- const require_portable_collection = require("./portable-collection-DL-1Wy1s.cjs");
7
- const require_kv_cache = require("./kv-cache-D4throaQ.cjs");
6
+ const require_portable_collection = require("./portable-collection-BHZq0F_c.cjs");
7
+ const require_kv_cache = require("./kv-cache-JOKAStz2.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -2779,12 +2779,41 @@ async function isInAudience(object, actor, options = {}) {
2779
2779
  //#endregion
2780
2780
  //#region src/federation/inbox.ts
2781
2781
  const Temporal$10 = require_http.Temporal;
2782
+ /**
2783
+ * Checks whether an activity's ID and all of its actor IDs have the same
2784
+ * [FEP-fe34] origin. Activities without an ID have no origin to authenticate.
2785
+ *
2786
+ * Origins are compared by `getAuthenticationOrigin()`, so a portable activity
2787
+ * and its portable actors, including compatible identifiers on any gateway,
2788
+ * are compared by their DIDs. An ID that has no supported origin, such as an
2789
+ * opaque URN, never matches.
2790
+ *
2791
+ * The caller is responsible for rejecting a missing primary actor ID before
2792
+ * relying on this check.
2793
+ *
2794
+ * [FEP-fe34]: https://w3id.org/fep/fe34
2795
+ * @internal
2796
+ */
2797
+ function actorsHaveSameOrigin(activity) {
2798
+ if (activity.id == null) return true;
2799
+ const origin = require_http.getAuthenticationOrigin(activity.id);
2800
+ return origin != null && activity.actorIds.every((actor) => require_http.getAuthenticationOrigin(actor) === origin);
2801
+ }
2782
2802
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
2783
2803
  const logger = (0, _logtape_logtape.getLogger)([
2784
2804
  "fedify",
2785
2805
  "federation",
2786
2806
  "inbox"
2787
2807
  ]);
2808
+ if (activity.actorId == null) {
2809
+ logger.error("Missing actor.", { activity: json });
2810
+ span.setStatus({
2811
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2812
+ message: "Missing actor."
2813
+ });
2814
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
2815
+ return "missingActor";
2816
+ }
2788
2817
  let cacheKey = null;
2789
2818
  if (activity.id != null) {
2790
2819
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href);
@@ -2820,15 +2849,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2820
2849
  return "alreadyProcessed";
2821
2850
  }
2822
2851
  }
2823
- if (activity.actorId == null) {
2824
- logger.error("Missing actor.", { activity: json });
2825
- span.setStatus({
2826
- code: _opentelemetry_api.SpanStatusCode.ERROR,
2827
- message: "Missing actor."
2828
- });
2829
- require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
2830
- return "missingActor";
2831
- }
2832
2852
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
2833
2853
  if (queue != null) {
2834
2854
  const carrier = {};
@@ -4746,13 +4766,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
4746
4766
  * @param parameters The parameters for handling the collection.
4747
4767
  * @returns A promise that resolves to an HTTP response.
4748
4768
  */
4749
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4750
- const spanName = name$2.trim().replace(/\s+/g, "_");
4769
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4770
+ const spanName = name$1.trim().replace(/\s+/g, "_");
4751
4771
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
4752
4772
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
4753
4773
  const cursor = new URL(request.url).searchParams.get("cursor");
4754
4774
  const metricBase = {
4755
- kind: getCollectionMetricKind(name$2),
4775
+ kind: getCollectionMetricKind(name$1),
4756
4776
  page: cursor != null,
4757
4777
  dispatcher: "built_in"
4758
4778
  };
@@ -4798,7 +4818,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4798
4818
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4799
4819
  return await onNotFound(request);
4800
4820
  }
4801
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
4821
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4802
4822
  itemCount = items.length;
4803
4823
  span.setAttribute("fedify.collection.items", itemCount);
4804
4824
  return items;
@@ -4840,7 +4860,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4840
4860
  } else {
4841
4861
  const uri = new URL(portable == null ? baseUri : pageBase);
4842
4862
  uri.searchParams.set("cursor", cursor);
4843
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
4863
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
4844
4864
  kind: _opentelemetry_api.SpanKind.SERVER,
4845
4865
  attributes: {
4846
4866
  "activitypub.collection.id": uri.href,
@@ -4856,7 +4876,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4856
4876
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4857
4877
  return await onNotFound(request);
4858
4878
  }
4859
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
4879
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4860
4880
  itemCount = items.length;
4861
4881
  span.setAttribute("fedify.collection.items", itemCount);
4862
4882
  return {
@@ -6005,11 +6025,49 @@ async function handleInboxInternal(request, parameters, span) {
6005
6025
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
6006
6026
  }
6007
6027
  }
6028
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
6029
+ observation.authentication = {
6030
+ status: "rejected",
6031
+ reason: { type: "verificationFailed" }
6032
+ };
6033
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
6034
+ activity: json,
6035
+ recipient,
6036
+ activityId: activity.id.href,
6037
+ actorIds: activity.actorIds.map((actor) => actor.href)
6038
+ });
6039
+ span.setStatus({
6040
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
6041
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
6042
+ });
6043
+ return new Response("The activity and its actors do not have the same origin.", {
6044
+ status: 401,
6045
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6046
+ });
6047
+ }
6008
6048
  observation.authentication = skipSignatureVerification ? { status: "skipped" } : {
6009
6049
  status: "verified",
6010
6050
  attempts: observation.verification.attempts.filter((attempt) => attempt.status === "verified")
6011
6051
  };
6012
6052
  observation.stage = "dispatch";
6053
+ if (activity.actorId == null) {
6054
+ observation.result = {
6055
+ disposition: "rejected",
6056
+ reason: "missingActor"
6057
+ };
6058
+ logger.error("Missing actor.", {
6059
+ activity: json,
6060
+ recipient
6061
+ });
6062
+ span.setStatus({
6063
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
6064
+ message: "Missing actor."
6065
+ });
6066
+ return new Response("Missing actor.", {
6067
+ status: 400,
6068
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6069
+ });
6070
+ }
6013
6071
  const routeResult = await routeActivity({
6014
6072
  context: ctx,
6015
6073
  json,
@@ -6230,8 +6288,8 @@ var CustomCollectionHandler = class {
6230
6288
  * @param CollectionPage The CollectionPage constructor.
6231
6289
  * @param filterPredicate Optional filter predicate for items.
6232
6290
  */
6233
- constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
6234
- this.name = name$1;
6291
+ constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
6292
+ this.name = name$2;
6235
6293
  this.values = values;
6236
6294
  this.context = context;
6237
6295
  this.callbacks = callbacks;
@@ -9357,6 +9415,35 @@ var FederationImpl = class extends FederationBuilderImpl {
9357
9415
  span.setAttribute("activitypub.activity.id", activity.id.href);
9358
9416
  listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
9359
9417
  }
9418
+ if (activity.actorId == null) {
9419
+ logger.error("Missing actor in queued incoming activity.", {
9420
+ activityId: activity.id?.href,
9421
+ activity: message.activity,
9422
+ recipient: message.identifier
9423
+ });
9424
+ listenerSpan.setStatus({
9425
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
9426
+ message: "Missing actor."
9427
+ });
9428
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
9429
+ listenerSpan.end();
9430
+ return;
9431
+ }
9432
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
9433
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
9434
+ activityId: activity.id.href,
9435
+ actorIds: activity.actorIds.map((actor) => actor.href),
9436
+ activity: message.activity,
9437
+ recipient: message.identifier
9438
+ });
9439
+ listenerSpan.setStatus({
9440
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
9441
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
9442
+ });
9443
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
9444
+ listenerSpan.end();
9445
+ return;
9446
+ }
9360
9447
  cacheKey = activity.id == null ? null : [
9361
9448
  ...this.kvPrefixes.activityIdempotence,
9362
9449
  context.origin,
@@ -11433,6 +11520,20 @@ var ContextImpl = class ContextImpl {
11433
11520
  recipient,
11434
11521
  activity: json
11435
11522
  });
11523
+ if (activity.actorId == null) {
11524
+ logger.debug("Activity is missing an actor; unable to verify.", {
11525
+ recipient,
11526
+ activity: json
11527
+ });
11528
+ return false;
11529
+ }
11530
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
11531
+ logger.debug("Activity has actors from different origins; unable to verify.", {
11532
+ recipient,
11533
+ activity: json
11534
+ });
11535
+ return false;
11536
+ }
11436
11537
  const routeResult = await routeActivity({
11437
11538
  context: this,
11438
11539
  json,
@@ -15,7 +15,7 @@ import serialize from "json-canon";
15
15
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
16
16
  //#region deno.json
17
17
  var name = "@fedify/fedify";
18
- var version = "2.4.2";
18
+ var version = "2.4.3";
19
19
  //#endregion
20
20
  //#region src/sig/portable-key-id.ts
21
21
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -17,7 +17,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
17
17
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
18
18
  //#region deno.json
19
19
  var name = "@fedify/fedify";
20
- var version = "2.4.2";
20
+ var version = "2.4.3";
21
21
  //#endregion
22
22
  //#region src/sig/portable-key-id.ts
23
23
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -3,12 +3,12 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { m as parseKeyIdString } from "./portable-key-id-Ca30-hOa.mjs";
5
5
  import { a as verificationObservation, i as triedKey, n as observeCheck, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
6
- import { n as version, t as name } from "./deno-BZ4z-lJJ.mjs";
7
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CRv6_IBg.mjs";
6
+ import { n as version, t as name } from "./deno-BEgaR4dC.mjs";
7
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DAhJVtgg.mjs";
8
8
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
9
9
  import { a as promoteKeyOwnerEvidence } from "./key-owner-evidence-APB6NpSk.mjs";
10
- import { S as fetchKeyDetailed, T as validateCryptoKey, v as bypassKeyCacheReads } from "./ld-Dkkyk65e.mjs";
11
- import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-Cdw3vfl-.mjs";
10
+ import { S as fetchKeyDetailed, T as validateCryptoKey, v as bypassKeyCacheReads } from "./ld-Qk4WqslS.mjs";
11
+ import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-BY6G_X28.mjs";
12
12
  import { getLogger } from "@logtape/logtape";
13
13
  import { CryptographicKey } from "@fedify/vocab";
14
14
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -0,0 +1,188 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as getAuthenticationOrigin } from "./portable-key-id-Ca30-hOa.mjs";
5
+ import { n as version, t as name } from "./deno-BEgaR4dC.mjs";
6
+ import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-DAhJVtgg.mjs";
7
+ import { getLogger } from "@logtape/logtape";
8
+ import { getTypeId } from "@fedify/vocab";
9
+ import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
10
+ //#region src/federation/inbox.ts
11
+ /**
12
+ * Checks whether an activity's ID and all of its actor IDs have the same
13
+ * [FEP-fe34] origin. Activities without an ID have no origin to authenticate.
14
+ *
15
+ * Origins are compared by `getAuthenticationOrigin()`, so a portable activity
16
+ * and its portable actors, including compatible identifiers on any gateway,
17
+ * are compared by their DIDs. An ID that has no supported origin, such as an
18
+ * opaque URN, never matches.
19
+ *
20
+ * The caller is responsible for rejecting a missing primary actor ID before
21
+ * relying on this check.
22
+ *
23
+ * [FEP-fe34]: https://w3id.org/fep/fe34
24
+ * @internal
25
+ */
26
+ function actorsHaveSameOrigin(activity) {
27
+ if (activity.id == null) return true;
28
+ const origin = getAuthenticationOrigin(activity.id);
29
+ return origin != null && activity.actorIds.every((actor) => getAuthenticationOrigin(actor) === origin);
30
+ }
31
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
32
+ const logger = getLogger([
33
+ "fedify",
34
+ "federation",
35
+ "inbox"
36
+ ]);
37
+ if (activity.actorId == null) {
38
+ logger.error("Missing actor.", { activity: json });
39
+ span.setStatus({
40
+ code: SpanStatusCode.ERROR,
41
+ message: "Missing actor."
42
+ });
43
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
44
+ return "missingActor";
45
+ }
46
+ let cacheKey = null;
47
+ if (activity.id != null) {
48
+ const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
49
+ const strategy = idempotencyStrategy ?? "per-inbox";
50
+ let keyString;
51
+ if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
52
+ else switch (strategy) {
53
+ case "global":
54
+ keyString = activity.id.href;
55
+ break;
56
+ case "per-origin":
57
+ keyString = `${ctx.origin}\n${activity.id.href}`;
58
+ break;
59
+ case "per-inbox":
60
+ keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
61
+ break;
62
+ default: keyString = `${ctx.origin}\n${activity.id.href}`;
63
+ }
64
+ if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
65
+ }
66
+ if (cacheKey != null) {
67
+ if (await kv.get(cacheKey) === true) {
68
+ logger.debug("Activity {activityId} has already been processed.", {
69
+ activityId: activity.id?.href,
70
+ activity: json,
71
+ recipient
72
+ });
73
+ span.setStatus({
74
+ code: SpanStatusCode.UNSET,
75
+ message: `Activity ${activity.id?.href} has already been processed.`
76
+ });
77
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
78
+ return "alreadyProcessed";
79
+ }
80
+ }
81
+ span.setAttribute("activitypub.actor.id", activity.actorId.href);
82
+ if (queue != null) {
83
+ const carrier = {};
84
+ propagation.inject(context.active(), carrier);
85
+ try {
86
+ await queue.enqueue({
87
+ type: "inbox",
88
+ id: crypto.randomUUID(),
89
+ baseUrl: ctx.origin,
90
+ activity: originalJson ?? json,
91
+ ...normalizedActivity == null ? {} : { normalizedActivity },
92
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
93
+ identifier: recipient,
94
+ attempt: 0,
95
+ started: (/* @__PURE__ */ new Date()).toISOString(),
96
+ traceContext: carrier
97
+ });
98
+ } catch (error) {
99
+ logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
100
+ error,
101
+ activityId: activity.id?.href,
102
+ activity: json,
103
+ recipient
104
+ });
105
+ span.setStatus({
106
+ code: SpanStatusCode.ERROR,
107
+ message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
108
+ });
109
+ throw error;
110
+ }
111
+ getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
112
+ role: "inbox",
113
+ queue,
114
+ activityType: getTypeId(activity).href
115
+ }, 0);
116
+ recordInboxActivity(meterProvider, "queued", getTypeId(activity).href);
117
+ logger.info("Activity {activityId} is enqueued.", {
118
+ activityId: activity.id?.href,
119
+ activity: json,
120
+ recipient
121
+ });
122
+ return "enqueued";
123
+ }
124
+ tracerProvider = tracerProvider ?? trace.getTracerProvider();
125
+ return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
126
+ const dispatched = inboxListeners?.dispatchWithClass(activity);
127
+ if (dispatched == null) {
128
+ logger.error("Unsupported activity type:\n{activity}", {
129
+ activity: json,
130
+ recipient
131
+ });
132
+ span.setStatus({
133
+ code: SpanStatusCode.UNSET,
134
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
135
+ });
136
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
137
+ span.end();
138
+ return "unsupportedActivity";
139
+ }
140
+ const { class: cls, listener } = dispatched;
141
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
142
+ try {
143
+ const activityType = getTypeId(activity).href;
144
+ const started = performance.now();
145
+ try {
146
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
147
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
148
+ } finally {
149
+ getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
150
+ }
151
+ } catch (error) {
152
+ try {
153
+ await inboxErrorHandler?.(ctx, error);
154
+ } catch (error) {
155
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
156
+ error,
157
+ activityId: activity.id?.href,
158
+ activity: json,
159
+ recipient
160
+ });
161
+ }
162
+ logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
163
+ error,
164
+ activityId: activity.id?.href,
165
+ activity: json,
166
+ recipient
167
+ });
168
+ span.setStatus({
169
+ code: SpanStatusCode.ERROR,
170
+ message: String(error)
171
+ });
172
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
173
+ span.end();
174
+ return "error";
175
+ }
176
+ recordInboxActivity(meterProvider, "processed", getTypeId(activity).href);
177
+ if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
178
+ logger.info("Activity {activityId} has been processed.", {
179
+ activityId: activity.id?.href,
180
+ activity: json,
181
+ recipient
182
+ });
183
+ span.end();
184
+ return "success";
185
+ });
186
+ }
187
+ //#endregion
188
+ export { routeActivity as n, actorsHaveSameOrigin as t };
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-CRv6_IBg.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-DAhJVtgg.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { G as validateCryptoKey, ot as recordDocumentCache, t as doubleKnock } from "./http-Qq7jR2rC.js";
3
+ import { G as validateCryptoKey, ot as recordDocumentCache, t as doubleKnock } from "./http-B5pRXkmQ.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, resolveDocumentLoaderTimeout, validatePublicUrl, withDocumentLoaderTimeout } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
@@ -62,6 +62,8 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
62
62
  }, options);
63
63
  }
64
64
  async function validateUrl(url, options) {
65
+ const protocol = new URL(url).protocol;
66
+ if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
65
67
  if (!allowPrivateAddress) {
66
68
  try {
67
69
  await validatePublicUrl(url);
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-x1satM4y.cjs");
2
+ const require_http = require("./http-BFC8b7mH.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
5
  let es_toolkit = require("es-toolkit");
@@ -61,6 +61,8 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
61
61
  }, options);
62
62
  }
63
63
  async function validateUrl(url, options) {
64
+ const protocol = new URL(url).protocol;
65
+ if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
64
66
  if (!allowPrivateAddress) {
65
67
  try {
66
68
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
@@ -3,8 +3,8 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { c as isPortableActorDocument, d as isPortableUri, l as isPortableId, m as parseKeyIdString, s as isCompatibleKeyId } from "./portable-key-id-Ca30-hOa.mjs";
5
5
  import { a as verificationObservation, i as triedKey, n as observeCheck, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
6
- import { n as version, t as name } from "./deno-BZ4z-lJJ.mjs";
7
- import { _ as recordKeyLookup, n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch, t as classifyFetchError } from "./metrics-CRv6_IBg.mjs";
6
+ import { n as version, t as name } from "./deno-BEgaR4dC.mjs";
7
+ import { _ as recordKeyLookup, n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch, t as classifyFetchError } from "./metrics-DAhJVtgg.mjs";
8
8
  import { n as attachKeyOwnerEvidence, t as MAX_PORTABLE_KEY_TTL } from "./key-owner-evidence-APB6NpSk.mjs";
9
9
  import { getLogger } from "@logtape/logtape";
10
10
  import { Activity, CryptographicKey, Multikey, Object as Object$1, getTypeId, isActor } from "@fedify/vocab";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BZ4z-lJJ.mjs";
4
+ import { n as version, t as name } from "./deno-BEgaR4dC.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts