@fedify/fedify 2.5.0-dev.2378 → 2.5.0-dev.2424

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 (92) hide show
  1. package/dist/{builder-J1K0IJj6.mjs → builder-crKu0U3j.mjs} +2 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-DtmVrMV7.mjs → compound-proof-D5pMfQau.mjs} +2 -2
  6. package/dist/{context-C2-UDo2c.d.ts → context-2wLkQrjb.d.ts} +11 -0
  7. package/dist/{context-BjD_CTxh.d.cts → context-DPocjp1l.d.cts} +11 -0
  8. package/dist/{deno-Ci6tSK9-.mjs → deno-Dgtb5dvg.mjs} +1 -1
  9. package/dist/{docloader-DZ9h6KJS.mjs → docloader-C8QI5jg2.mjs} +4 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  13. package/dist/federation/handler.test.mjs +258 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox-report.test.mjs +30 -5
  17. package/dist/federation/inbox.test.mjs +43 -0
  18. package/dist/federation/metrics.test.mjs +1 -1
  19. package/dist/federation/middleware.test.mjs +254 -11
  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/outgoing-proof.test.mjs +4 -4
  25. package/dist/federation/portable-actor.test.mjs +3 -3
  26. package/dist/federation/portable-chain.test.mjs +3 -3
  27. package/dist/federation/portable-collection.test.mjs +4 -4
  28. package/dist/federation/portable-delivery.test.mjs +2 -2
  29. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  30. package/dist/federation/portable-inbox.test.mjs +4 -4
  31. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  32. package/dist/federation/portable-route-activity.test.mjs +2 -2
  33. package/dist/federation/portable.test.mjs +3 -3
  34. package/dist/federation/send.test.mjs +3 -3
  35. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  36. package/dist/federation/tasks/tasks.test.mjs +2 -2
  37. package/dist/federation/temporal.test.mjs +1 -1
  38. package/dist/federation/webfinger.test.mjs +1 -1
  39. package/dist/{federation-lzy7M3hT.cjs → federation-XZF9Yna_.cjs} +133 -21
  40. package/dist/{http-CFyizTHa.mjs → http-CO8l72H1.mjs} +4 -4
  41. package/dist/{http-Cmz7x6Iw.js → http-CRRQ6LBq.js} +1 -1
  42. package/dist/{http-D1RPHoq3.cjs → http-DZpLusNJ.cjs} +1 -1
  43. package/dist/inbox-q6uJubdj.mjs +188 -0
  44. package/dist/{kv-cache-rOQX3I3c.mjs → kv-cache-C-20uhXH.mjs} +1 -1
  45. package/dist/{kv-cache-C0-HuvwP.js → kv-cache-D5EHCk1_.js} +3 -1
  46. package/dist/{kv-cache-Jym5nP3J.cjs → kv-cache-DB0oTO06.cjs} +3 -1
  47. package/dist/{ld-DJ--ZUpr.mjs → ld-DqV2dhgT.mjs} +2 -2
  48. package/dist/{metrics-Dwgav7s_.mjs → metrics--5oGNIHa.mjs} +1 -1
  49. package/dist/{middleware-BbBsa5iR.mjs → middleware-BahtLJeT.mjs} +115 -180
  50. package/dist/{middleware-CJWxuUy5.mjs → middleware-Cna-mkwy.mjs} +1 -1
  51. package/dist/{middleware-CqgRIsWM.js → middleware-Mn7MeLDZ.js} +133 -21
  52. package/dist/{mod-BBPd4M_0.d.cts → mod-DKGoPoQC.d.cts} +1 -1
  53. package/dist/{mod-CUYBGKof.d.ts → mod-Dm-7RdhR.d.ts} +1 -1
  54. package/dist/mod.cjs +4 -4
  55. package/dist/mod.d.cts +2 -2
  56. package/dist/mod.d.ts +2 -2
  57. package/dist/mod.js +4 -4
  58. package/dist/nodeinfo/handler.test.mjs +1 -1
  59. package/dist/{owner-Btd7SwMj.mjs → owner-Cb6Kalag.mjs} +3 -3
  60. package/dist/{portable-collection-DwQT28O2.js → portable-collection-BweSVeOo.js} +1 -1
  61. package/dist/{portable-collection-BvWxS1sL.cjs → portable-collection-C3FBNHhB.cjs} +1 -1
  62. package/dist/{portable-collection-gb9V8XEO.mjs → portable-collection-DatZLGgt.mjs} +2 -2
  63. package/dist/{portable-key-Cwti99nP.mjs → portable-key-C2jzDc2S.mjs} +1 -1
  64. package/dist/{proof-Bwzv6mXZ.mjs → proof-BpQd-cat.mjs} +3 -3
  65. package/dist/{send-BrQYUSb4.mjs → send-CrgC-GYC.mjs} +3 -3
  66. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  67. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  68. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  69. package/dist/sig/compound-proof.test.mjs +2 -2
  70. package/dist/sig/http.test.mjs +2 -2
  71. package/dist/sig/key.test.mjs +1 -1
  72. package/dist/sig/ld.test.mjs +1 -1
  73. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  74. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  75. package/dist/sig/mod.cjs +2 -2
  76. package/dist/sig/mod.js +2 -2
  77. package/dist/sig/owner.test.mjs +1 -1
  78. package/dist/sig/portable-collection.test.mjs +1 -1
  79. package/dist/sig/portable-interop.test.mjs +3 -3
  80. package/dist/sig/portable-key.test.mjs +5 -5
  81. package/dist/sig/proof.test.mjs +3 -3
  82. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  83. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  84. package/dist/sig/verification.test.mjs +3 -3
  85. package/dist/{temporal-BRaCTxVe.mjs → temporal-BjBxALiF.mjs} +1 -1
  86. package/dist/testing/kv.test.mjs +1 -1
  87. package/dist/testing/mod.d.mts +11 -0
  88. package/dist/utils/docloader.test.mjs +112 -37
  89. package/dist/utils/kv-cache.test.mjs +1 -1
  90. package/dist/utils/mod.cjs +1 -1
  91. package/dist/utils/mod.js +1 -1
  92. package/package.json +6 -6
@@ -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-D1RPHoq3.cjs");
4
+ const require_http = require("./http-DZpLusNJ.cjs");
5
5
  const require_types = require("./types-poIao40f.cjs");
6
- const require_portable_collection = require("./portable-collection-BvWxS1sL.cjs");
7
- const require_kv_cache = require("./kv-cache-Jym5nP3J.cjs");
6
+ const require_portable_collection = require("./portable-collection-C3FBNHhB.cjs");
7
+ const require_kv_cache = require("./kv-cache-DB0oTO06.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");
@@ -2807,12 +2807,41 @@ async function isInAudience(object, actor, options = {}) {
2807
2807
  //#endregion
2808
2808
  //#region src/federation/inbox.ts
2809
2809
  const Temporal$10 = require_http.Temporal;
2810
+ /**
2811
+ * Checks whether an activity's ID and all of its actor IDs have the same
2812
+ * [FEP-fe34] origin. Activities without an ID have no origin to authenticate.
2813
+ *
2814
+ * Origins are compared by `getAuthenticationOrigin()`, so a portable activity
2815
+ * and its portable actors, including compatible identifiers on any gateway,
2816
+ * are compared by their DIDs. An ID that has no supported origin, such as an
2817
+ * opaque URN, never matches.
2818
+ *
2819
+ * The caller is responsible for rejecting a missing primary actor ID before
2820
+ * relying on this check.
2821
+ *
2822
+ * [FEP-fe34]: https://w3id.org/fep/fe34
2823
+ * @internal
2824
+ */
2825
+ function actorsHaveSameOrigin(activity) {
2826
+ if (activity.id == null) return true;
2827
+ const origin = require_http.getAuthenticationOrigin(activity.id);
2828
+ return origin != null && activity.actorIds.every((actor) => require_http.getAuthenticationOrigin(actor) === origin);
2829
+ }
2810
2830
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
2811
2831
  const logger = (0, _logtape_logtape.getLogger)([
2812
2832
  "fedify",
2813
2833
  "federation",
2814
2834
  "inbox"
2815
2835
  ]);
2836
+ if (activity.actorId == null) {
2837
+ logger.error("Missing actor.", { activity: json });
2838
+ span.setStatus({
2839
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2840
+ message: "Missing actor."
2841
+ });
2842
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
2843
+ return "missingActor";
2844
+ }
2816
2845
  let cacheKey = null;
2817
2846
  if (activity.id != null) {
2818
2847
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href);
@@ -2848,15 +2877,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2848
2877
  return "alreadyProcessed";
2849
2878
  }
2850
2879
  }
2851
- if (activity.actorId == null) {
2852
- logger.error("Missing actor.", { activity: json });
2853
- span.setStatus({
2854
- code: _opentelemetry_api.SpanStatusCode.ERROR,
2855
- message: "Missing actor."
2856
- });
2857
- require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
2858
- return "missingActor";
2859
- }
2860
2880
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
2861
2881
  if (queue != null) {
2862
2882
  const carrier = {};
@@ -4778,13 +4798,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
4778
4798
  * @param parameters The parameters for handling the collection.
4779
4799
  * @returns A promise that resolves to an HTTP response.
4780
4800
  */
4781
- async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4782
- const spanName = name$1.trim().replace(/\s+/g, "_");
4801
+ async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4802
+ const spanName = name$2.trim().replace(/\s+/g, "_");
4783
4803
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
4784
4804
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
4785
4805
  const cursor = new URL(request.url).searchParams.get("cursor");
4786
4806
  const metricBase = {
4787
- kind: getCollectionMetricKind(name$1),
4807
+ kind: getCollectionMetricKind(name$2),
4788
4808
  page: cursor != null,
4789
4809
  dispatcher: "built_in"
4790
4810
  };
@@ -4830,7 +4850,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
4830
4850
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4831
4851
  return await onNotFound(request);
4832
4852
  }
4833
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
4853
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
4834
4854
  itemCount = items.length;
4835
4855
  span.setAttribute("fedify.collection.items", itemCount);
4836
4856
  return items;
@@ -4872,7 +4892,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
4872
4892
  } else {
4873
4893
  const uri = new URL(portable == null ? baseUri : pageBase);
4874
4894
  uri.searchParams.set("cursor", cursor);
4875
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
4895
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
4876
4896
  kind: _opentelemetry_api.SpanKind.SERVER,
4877
4897
  attributes: {
4878
4898
  "activitypub.collection.id": uri.href,
@@ -4888,7 +4908,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
4888
4908
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
4889
4909
  return await onNotFound(request);
4890
4910
  }
4891
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
4911
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
4892
4912
  itemCount = items.length;
4893
4913
  span.setAttribute("fedify.collection.items", itemCount);
4894
4914
  return {
@@ -6041,11 +6061,53 @@ async function handleInboxInternal(request, parameters, span) {
6041
6061
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
6042
6062
  }
6043
6063
  }
6064
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
6065
+ observation.authentication = {
6066
+ status: "rejected",
6067
+ reason: {
6068
+ type: "activityOriginMismatch",
6069
+ activityId: new URL(activity.id.href),
6070
+ actorIds: activity.actorIds.map((id) => new URL(id.href))
6071
+ }
6072
+ };
6073
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
6074
+ activity: json,
6075
+ recipient,
6076
+ activityId: activity.id.href,
6077
+ actorIds: activity.actorIds.map((actor) => actor.href)
6078
+ });
6079
+ span.setStatus({
6080
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
6081
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
6082
+ });
6083
+ return new Response("The activity and its actors do not have the same origin.", {
6084
+ status: 401,
6085
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6086
+ });
6087
+ }
6044
6088
  observation.authentication = skipSignatureVerification ? { status: "skipped" } : {
6045
6089
  status: "verified",
6046
6090
  attempts: observation.verification.attempts.filter((attempt) => attempt.status === "verified")
6047
6091
  };
6048
6092
  observation.stage = "dispatch";
6093
+ if (activity.actorId == null) {
6094
+ observation.result = {
6095
+ disposition: "rejected",
6096
+ reason: "missingActor"
6097
+ };
6098
+ logger.error("Missing actor.", {
6099
+ activity: json,
6100
+ recipient
6101
+ });
6102
+ span.setStatus({
6103
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
6104
+ message: "Missing actor."
6105
+ });
6106
+ return new Response("Missing actor.", {
6107
+ status: 400,
6108
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6109
+ });
6110
+ }
6049
6111
  const routeResult = await routeActivity({
6050
6112
  context: ctx,
6051
6113
  json,
@@ -6266,8 +6328,8 @@ var CustomCollectionHandler = class {
6266
6328
  * @param CollectionPage The CollectionPage constructor.
6267
6329
  * @param filterPredicate Optional filter predicate for items.
6268
6330
  */
6269
- constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
6270
- this.name = name$2;
6331
+ constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
6332
+ this.name = name$1;
6271
6333
  this.values = values;
6272
6334
  this.context = context;
6273
6335
  this.callbacks = callbacks;
@@ -9393,6 +9455,35 @@ var FederationImpl = class extends FederationBuilderImpl {
9393
9455
  span.setAttribute("activitypub.activity.id", activity.id.href);
9394
9456
  listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
9395
9457
  }
9458
+ if (activity.actorId == null) {
9459
+ logger.error("Missing actor in queued incoming activity.", {
9460
+ activityId: activity.id?.href,
9461
+ activity: message.activity,
9462
+ recipient: message.identifier
9463
+ });
9464
+ listenerSpan.setStatus({
9465
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
9466
+ message: "Missing actor."
9467
+ });
9468
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
9469
+ listenerSpan.end();
9470
+ return;
9471
+ }
9472
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
9473
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
9474
+ activityId: activity.id.href,
9475
+ actorIds: activity.actorIds.map((actor) => actor.href),
9476
+ activity: message.activity,
9477
+ recipient: message.identifier
9478
+ });
9479
+ listenerSpan.setStatus({
9480
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
9481
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
9482
+ });
9483
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
9484
+ listenerSpan.end();
9485
+ return;
9486
+ }
9396
9487
  cacheKey = activity.id == null ? null : [
9397
9488
  ...this.kvPrefixes.activityIdempotence,
9398
9489
  context.origin,
@@ -11469,6 +11560,20 @@ var ContextImpl = class ContextImpl {
11469
11560
  recipient,
11470
11561
  activity: json
11471
11562
  });
11563
+ if (activity.actorId == null) {
11564
+ logger.debug("Activity is missing an actor; unable to verify.", {
11565
+ recipient,
11566
+ activity: json
11567
+ });
11568
+ return false;
11569
+ }
11570
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
11571
+ logger.debug("Activity has actors from different origins; unable to verify.", {
11572
+ recipient,
11573
+ activity: json
11574
+ });
11575
+ return false;
11576
+ }
11472
11577
  const routeResult = await routeActivity({
11473
11578
  context: this,
11474
11579
  json,
@@ -11591,6 +11696,12 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
11591
11696
  #signedKey = void 0;
11592
11697
  async getSignedKey(options = {}) {
11593
11698
  if (this.#signedKey != null) return this.#signedKey;
11699
+ const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
11700
+ documentLoader: options.documentLoader ?? this.documentLoader,
11701
+ contextLoader: options.contextLoader ?? this.contextLoader,
11702
+ tracerProvider: options.tracerProvider ?? this.tracerProvider,
11703
+ keyTtl: this.federation.publicKeyTtl
11704
+ });
11594
11705
  return this.#signedKey = await require_http.verifyRequest(this.request, {
11595
11706
  ...this,
11596
11707
  contextLoader: options.contextLoader ?? this.contextLoader,
@@ -11598,7 +11709,8 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
11598
11709
  timeWindow: this.federation.signatureTimeWindow,
11599
11710
  maxSignatures: this.federation.maxHttpSignatures,
11600
11711
  meterProvider: this.meterProvider,
11601
- tracerProvider: options.tracerProvider ?? this.tracerProvider
11712
+ tracerProvider: options.tracerProvider ?? this.tracerProvider,
11713
+ keyCache
11602
11714
  });
11603
11715
  }
11604
11716
  #signedKeyOwner = void 0;
@@ -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-Ci6tSK9-.mjs";
7
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Dwgav7s_.mjs";
6
+ import { n as version, t as name } from "./deno-Dgtb5dvg.mjs";
7
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics--5oGNIHa.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-DJ--ZUpr.mjs";
11
- import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-Cwti99nP.mjs";
10
+ import { S as fetchKeyDetailed, T as validateCryptoKey, v as bypassKeyCacheReads } from "./ld-DqV2dhgT.mjs";
11
+ import { n as resolvePortableActorKey, r as verifyPortableGatewayKeyDocument } from "./portable-key-C2jzDc2S.mjs";
12
12
  import { getLogger } from "@logtape/logtape";
13
13
  import { CryptographicKey } from "@fedify/vocab";
14
14
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -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.5.0-dev.2378+fea9c751";
18
+ var version = "2.5.0-dev.2424+97214632";
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.5.0-dev.2378+fea9c751";
20
+ var version = "2.5.0-dev.2424+97214632";
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;
@@ -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-Dgtb5dvg.mjs";
6
+ import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics--5oGNIHa.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-Dwgav7s_.mjs";
4
+ import { p as recordDocumentCache } from "./metrics--5oGNIHa.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 { ct as recordDocumentCache, q as validateCryptoKey, t as doubleKnock } from "./http-Cmz7x6Iw.js";
3
+ import { ct as recordDocumentCache, q as validateCryptoKey, t as doubleKnock } from "./http-CRRQ6LBq.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-D1RPHoq3.cjs");
2
+ const require_http = require("./http-DZpLusNJ.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-Ci6tSK9-.mjs";
7
- import { _ as recordKeyLookup, n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch, t as classifyFetchError } from "./metrics-Dwgav7s_.mjs";
6
+ import { n as version, t as name } from "./deno-Dgtb5dvg.mjs";
7
+ import { _ as recordKeyLookup, n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch, t as classifyFetchError } from "./metrics--5oGNIHa.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-Ci6tSK9-.mjs";
4
+ import { n as version, t as name } from "./deno-Dgtb5dvg.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts