@fedify/fedify 2.2.17 → 2.2.18

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 (63) hide show
  1. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-WyYSTDDX.mjs} +2 -20
  2. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-D4DNWrux.mjs} +19 -1
  3. package/dist/{builder-a-kl29PE.mjs → builder-DAbQyxmb.mjs} +2 -2
  4. package/dist/compat/public-audience.test.mjs +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{deno-Cya0rbEr.mjs → deno-JUTr6LYC.mjs} +1 -1
  7. package/dist/{docloader-Dz0Z1Y07.mjs → docloader-BGjq1F5U.mjs} +4 -2
  8. package/dist/federation/builder.test.mjs +3 -3
  9. package/dist/federation/collection.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +261 -8
  11. package/dist/federation/idempotency.test.mjs +3 -3
  12. package/dist/federation/inbox.test.mjs +25 -1
  13. package/dist/federation/kv.test.mjs +1 -1
  14. package/dist/federation/middleware.test.mjs +128 -10
  15. package/dist/federation/mod.cjs +1 -1
  16. package/dist/federation/mod.js +1 -1
  17. package/dist/federation/mq.test.mjs +2 -2
  18. package/dist/federation/negotiation.test.mjs +3 -3
  19. package/dist/federation/retry.test.mjs +2 -2
  20. package/dist/federation/router.test.mjs +2 -3
  21. package/dist/federation/send.test.mjs +7 -6
  22. package/dist/federation/temporal.test.mjs +3 -3
  23. package/dist/federation/webfinger.test.mjs +2 -2
  24. package/dist/{http-C_aA2Zx1.cjs → http-DEH_eppX.cjs} +1 -1
  25. package/dist/{http-DkzPSl5o.js → http-OSYYhKM8.js} +1 -1
  26. package/dist/{http-dobCLGG2.mjs → http-lCmKCZBI.mjs} +2 -2
  27. package/dist/inbox-DMATfdEI.mjs +162 -0
  28. package/dist/{key-BMHNd0gP.mjs → key-D0ahOVjf.mjs} +1 -1
  29. package/dist/{kv-cache-DVhfMtqE.js → kv-cache-B7X4p1y7.js} +3 -1
  30. package/dist/{kv-cache-Btp4kC_U.cjs → kv-cache-CPEV_4nZ.cjs} +3 -1
  31. package/dist/{ld-B_m0qw8j.mjs → ld-CJOXIehz.mjs} +2 -2
  32. package/dist/{middleware-BYxKsDHj.mjs → middleware-BgGJEcdp.mjs} +1 -1
  33. package/dist/{middleware-5qLdPLro.mjs → middleware-CHoUjmJm.mjs} +84 -167
  34. package/dist/{middleware-ZKHfZ7zj.cjs → middleware-D3hx1aiI.cjs} +1 -1
  35. package/dist/{middleware-C7mI18WE.cjs → middleware-QcuVRA-q.cjs} +96 -26
  36. package/dist/{middleware-BchkzbpG.js → middleware-aNUajYPY.js} +95 -25
  37. package/dist/mod.cjs +4 -4
  38. package/dist/mod.js +4 -4
  39. package/dist/nodeinfo/client.test.mjs +1 -1
  40. package/dist/nodeinfo/handler.test.mjs +2 -2
  41. package/dist/nodeinfo/types.test.mjs +2 -2
  42. package/dist/otel/exporter.test.mjs +1 -1
  43. package/dist/{owner-Dk85kXZz.mjs → owner-BUTMEkbn.mjs} +2 -2
  44. package/dist/{proof-ClpE9zhD.cjs → proof-BeY7TsiS.cjs} +1 -1
  45. package/dist/{proof-Bz6IdT2p.js → proof-Bm3xyn3D.js} +1 -1
  46. package/dist/{proof-DrGrnZVE.mjs → proof-DS1Zl6BX.mjs} +2 -2
  47. package/dist/{send-Co-E7yJn.mjs → send-fnPrh9Zm.mjs} +2 -2
  48. package/dist/sig/http.test.mjs +5 -5
  49. package/dist/sig/key.test.mjs +5 -5
  50. package/dist/sig/ld.test.mjs +4 -4
  51. package/dist/sig/mod.cjs +2 -2
  52. package/dist/sig/mod.js +2 -2
  53. package/dist/sig/owner.test.mjs +3 -3
  54. package/dist/sig/proof.test.mjs +4 -3
  55. package/dist/{std__assert-BVFj8-_V.mjs → std__assert-DNZ4Liil.mjs} +2 -2
  56. package/dist/{temporal-Bnwa_2NC.mjs → temporal-Biz6zK6e.mjs} +1 -1
  57. package/dist/utils/docloader.test.mjs +114 -39
  58. package/dist/utils/mod.cjs +1 -1
  59. package/dist/utils/mod.js +1 -1
  60. package/package.json +5 -5
  61. /package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-BkxW5QJI.mjs} +0 -0
  62. /package/dist/{negotiation-BHtZN6Y1.mjs → negotiation-SQvQgUqe.mjs} +0 -0
  63. /package/dist/{retry-bMXBL97A.mjs → retry-v_sGLH1d.mjs} +0 -0
@@ -2,25 +2,26 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-Cya0rbEr.mjs";
5
+ import { n as version, t as name } from "./deno-JUTr6LYC.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-Dd__NiUL.mjs";
7
- import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-BMHNd0gP.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-dobCLGG2.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-Dz0Z1Y07.mjs";
7
+ import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-D0ahOVjf.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-lCmKCZBI.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-BGjq1F5U.mjs";
10
10
  import { n as kvCache } from "./kv-cache-FsT1ljhs.mjs";
11
11
  import { t as KvKeyCache } from "./keycache-Dkc_sflV.mjs";
12
- import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-B_m0qw8j.mjs";
13
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-Dk85kXZz.mjs";
12
+ import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-CJOXIehz.mjs";
13
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-BUTMEkbn.mjs";
14
14
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-amccRMrV.mjs";
15
- import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-DrGrnZVE.mjs";
15
+ import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-DS1Zl6BX.mjs";
16
16
  import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-9F_u23dc.mjs";
17
17
  import { t as nodeInfoToJson } from "./types-J53Kw7so.mjs";
18
- import { t as FederationBuilderImpl } from "./builder-a-kl29PE.mjs";
18
+ import { t as FederationBuilderImpl } from "./builder-DAbQyxmb.mjs";
19
19
  import { t as buildCollectionSynchronizationHeader } from "./collection-D-HqUuA2.mjs";
20
- import { t as acceptsJsonLd } from "./negotiation-BHtZN6Y1.mjs";
21
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-Bnwa_2NC.mjs";
22
- import { t as createExponentialBackoffPolicy } from "./retry-bMXBL97A.mjs";
23
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-Co-E7yJn.mjs";
20
+ import { n as routeActivity, t as actorsHaveSameOrigin } from "./inbox-DMATfdEI.mjs";
21
+ import { t as acceptsJsonLd } from "./negotiation-SQvQgUqe.mjs";
22
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-Biz6zK6e.mjs";
23
+ import { t as createExponentialBackoffPolicy } from "./retry-v_sGLH1d.mjs";
24
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-fnPrh9Zm.mjs";
24
25
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
25
26
  import { lookupWebFinger } from "@fedify/webfinger";
26
27
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -154,147 +155,6 @@ function handleNodeInfoJrd(_request, context) {
154
155
  return Promise.resolve(response);
155
156
  }
156
157
  //#endregion
157
- //#region src/federation/inbox.ts
158
- async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
159
- const logger = getLogger([
160
- "fedify",
161
- "federation",
162
- "inbox"
163
- ]);
164
- let cacheKey = null;
165
- if (activity.id != null) {
166
- const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
167
- const strategy = idempotencyStrategy ?? "per-inbox";
168
- let keyString;
169
- if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
170
- else switch (strategy) {
171
- case "global":
172
- keyString = activity.id.href;
173
- break;
174
- case "per-origin":
175
- keyString = `${ctx.origin}\n${activity.id.href}`;
176
- break;
177
- case "per-inbox":
178
- keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
179
- break;
180
- default: keyString = `${ctx.origin}\n${activity.id.href}`;
181
- }
182
- if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
183
- }
184
- if (cacheKey != null) {
185
- if (await kv.get(cacheKey) === true) {
186
- logger.debug("Activity {activityId} has already been processed.", {
187
- activityId: activity.id?.href,
188
- activity: json,
189
- recipient
190
- });
191
- span.setStatus({
192
- code: SpanStatusCode.UNSET,
193
- message: `Activity ${activity.id?.href} has already been processed.`
194
- });
195
- return "alreadyProcessed";
196
- }
197
- }
198
- if (activity.actorId == null) {
199
- logger.error("Missing actor.", { activity: json });
200
- span.setStatus({
201
- code: SpanStatusCode.ERROR,
202
- message: "Missing actor."
203
- });
204
- return "missingActor";
205
- }
206
- span.setAttribute("activitypub.actor.id", activity.actorId.href);
207
- if (queue != null) {
208
- const carrier = {};
209
- propagation.inject(context.active(), carrier);
210
- try {
211
- await queue.enqueue({
212
- type: "inbox",
213
- id: crypto.randomUUID(),
214
- baseUrl: ctx.origin,
215
- activity: originalJson ?? json,
216
- ...normalizedActivity == null ? {} : { normalizedActivity },
217
- ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
218
- identifier: recipient,
219
- attempt: 0,
220
- started: (/* @__PURE__ */ new Date()).toISOString(),
221
- traceContext: carrier
222
- });
223
- } catch (error) {
224
- logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
225
- error,
226
- activityId: activity.id?.href,
227
- activity: json,
228
- recipient
229
- });
230
- span.setStatus({
231
- code: SpanStatusCode.ERROR,
232
- message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
233
- });
234
- throw error;
235
- }
236
- logger.info("Activity {activityId} is enqueued.", {
237
- activityId: activity.id?.href,
238
- activity: json,
239
- recipient
240
- });
241
- return "enqueued";
242
- }
243
- tracerProvider = tracerProvider ?? trace.getTracerProvider();
244
- return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
245
- const dispatched = inboxListeners?.dispatchWithClass(activity);
246
- if (dispatched == null) {
247
- logger.error("Unsupported activity type:\n{activity}", {
248
- activity: json,
249
- recipient
250
- });
251
- span.setStatus({
252
- code: SpanStatusCode.UNSET,
253
- message: `Unsupported activity type: ${getTypeId(activity).href}`
254
- });
255
- span.end();
256
- return "unsupportedActivity";
257
- }
258
- const { class: cls, listener } = dispatched;
259
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
260
- try {
261
- const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
262
- await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, getTypeId(activity).href), activity);
263
- } catch (error) {
264
- try {
265
- await inboxErrorHandler?.(ctx, error);
266
- } catch (error) {
267
- logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
268
- error,
269
- activityId: activity.id?.href,
270
- activity: json,
271
- recipient
272
- });
273
- }
274
- logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
275
- error,
276
- activityId: activity.id?.href,
277
- activity: json,
278
- recipient
279
- });
280
- span.setStatus({
281
- code: SpanStatusCode.ERROR,
282
- message: String(error)
283
- });
284
- span.end();
285
- return "error";
286
- }
287
- if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
288
- logger.info("Activity {activityId} has been processed.", {
289
- activityId: activity.id?.href,
290
- activity: json,
291
- recipient
292
- });
293
- span.end();
294
- return "success";
295
- });
296
- }
297
- //#endregion
298
158
  //#region src/federation/handler.ts
299
159
  const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
300
160
  function isRemoteContextLoadingFailure$1(error) {
@@ -1160,6 +1020,36 @@ async function handleInboxInternal(request, parameters, span) {
1160
1020
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1161
1021
  }
1162
1022
  }
1023
+ if (activity.actorId == null) {
1024
+ logger.error("Missing actor.", {
1025
+ activity: json,
1026
+ recipient
1027
+ });
1028
+ span.setStatus({
1029
+ code: SpanStatusCode.ERROR,
1030
+ message: "Missing actor."
1031
+ });
1032
+ return new Response("Missing actor.", {
1033
+ status: 400,
1034
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1035
+ });
1036
+ }
1037
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
1038
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
1039
+ activity: json,
1040
+ recipient,
1041
+ activityId: activity.id.href,
1042
+ actorIds: activity.actorIds.map((actor) => actor.href)
1043
+ });
1044
+ span.setStatus({
1045
+ code: SpanStatusCode.ERROR,
1046
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
1047
+ });
1048
+ return new Response("The activity and its actors do not have the same origin.", {
1049
+ status: 401,
1050
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1051
+ });
1052
+ }
1163
1053
  const routeResult = await routeActivity({
1164
1054
  context: ctx,
1165
1055
  json,
@@ -2360,6 +2250,33 @@ var FederationImpl = class extends FederationBuilderImpl {
2360
2250
  activity = await Activity.fromJsonLd(parseInput, parseContext);
2361
2251
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
2362
2252
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2253
+ if (activity.actorId == null) {
2254
+ logger.error("Missing actor in queued incoming activity.", {
2255
+ activityId: activity.id?.href,
2256
+ activity: message.activity,
2257
+ recipient: message.identifier
2258
+ });
2259
+ span.setStatus({
2260
+ code: SpanStatusCode.ERROR,
2261
+ message: "Missing actor."
2262
+ });
2263
+ span.end();
2264
+ return;
2265
+ }
2266
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
2267
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
2268
+ activityId: activity.id.href,
2269
+ actorIds: activity.actorIds.map((actor) => actor.href),
2270
+ activity: message.activity,
2271
+ recipient: message.identifier
2272
+ });
2273
+ span.setStatus({
2274
+ code: SpanStatusCode.ERROR,
2275
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
2276
+ });
2277
+ span.end();
2278
+ return;
2279
+ }
2363
2280
  cacheKey = activity.id == null ? null : [
2364
2281
  ...this.kvPrefixes.activityIdempotence,
2365
2282
  context.origin,
@@ -3521,20 +3438,6 @@ var ContextImpl = class ContextImpl {
3521
3438
  activity: await fetched.toJsonLd({ contextLoader })
3522
3439
  });
3523
3440
  return false;
3524
- } else if (fetched.actorIds.length < 1) {
3525
- logger.debug("Fetched activity object is missing an actor; unable to verify.", {
3526
- recipient,
3527
- activity: await fetched.toJsonLd({ contextLoader })
3528
- });
3529
- return false;
3530
- }
3531
- const activityId = fetched.id;
3532
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
3533
- logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
3534
- recipient,
3535
- activity: await fetched.toJsonLd({ contextLoader })
3536
- });
3537
- return false;
3538
3441
  }
3539
3442
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
3540
3443
  activity = fetched;
@@ -3543,6 +3446,20 @@ var ContextImpl = class ContextImpl {
3543
3446
  recipient,
3544
3447
  activity: json
3545
3448
  });
3449
+ if (activity.actorId == null) {
3450
+ logger.debug("Activity is missing an actor; unable to verify.", {
3451
+ recipient,
3452
+ activity: json
3453
+ });
3454
+ return false;
3455
+ }
3456
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
3457
+ logger.debug("Activity has actors from different origins; unable to verify.", {
3458
+ recipient,
3459
+ activity: json
3460
+ });
3461
+ return false;
3462
+ }
3546
3463
  const routeResult = await routeActivity({
3547
3464
  context: this,
3548
3465
  json,
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-C7mI18WE.cjs");
3
+ const require_middleware = require("./middleware-QcuVRA-q.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-C_aA2Zx1.cjs");
5
+ const require_http = require("./http-DEH_eppX.cjs");
6
6
  const require_types = require("./types-DRPjmHVV.cjs");
7
- const require_proof = require("./proof-ClpE9zhD.cjs");
8
- const require_kv_cache = require("./kv-cache-Btp4kC_U.cjs");
7
+ const require_proof = require("./proof-BeY7TsiS.cjs");
8
+ const require_kv_cache = require("./kv-cache-CPEV_4nZ.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -213,7 +213,7 @@ var FederationBuilderImpl = class {
213
213
  this.collectionTypeIds = {};
214
214
  }
215
215
  async build(options) {
216
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-ZKHfZ7zj.cjs"));
216
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-D3hx1aiI.cjs"));
217
217
  const f = new FederationImpl(options);
218
218
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
219
219
  f.router = this.router.clone();
@@ -782,12 +782,33 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
782
782
  }
783
783
  //#endregion
784
784
  //#region src/federation/inbox.ts
785
+ /**
786
+ * Checks whether an activity's ID and all of its actor IDs have the same
787
+ * non-opaque origin. Activities without an ID have no origin to authenticate.
788
+ *
789
+ * The caller is responsible for rejecting a missing primary actor ID before
790
+ * relying on this check.
791
+ * @internal
792
+ */
793
+ function actorsHaveSameOrigin(activity) {
794
+ if (activity.id == null) return true;
795
+ const origin = activity.id.origin;
796
+ return origin !== "null" && activity.actorIds.every((actor) => actor.origin === origin);
797
+ }
785
798
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
786
799
  const logger = (0, _logtape_logtape.getLogger)([
787
800
  "fedify",
788
801
  "federation",
789
802
  "inbox"
790
803
  ]);
804
+ if (activity.actorId == null) {
805
+ logger.error("Missing actor.", { activity: json });
806
+ span.setStatus({
807
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
808
+ message: "Missing actor."
809
+ });
810
+ return "missingActor";
811
+ }
791
812
  let cacheKey = null;
792
813
  if (activity.id != null) {
793
814
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href);
@@ -822,14 +843,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
822
843
  return "alreadyProcessed";
823
844
  }
824
845
  }
825
- if (activity.actorId == null) {
826
- logger.error("Missing actor.", { activity: json });
827
- span.setStatus({
828
- code: _opentelemetry_api.SpanStatusCode.ERROR,
829
- message: "Missing actor."
830
- });
831
- return "missingActor";
832
- }
833
846
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
834
847
  if (queue != null) {
835
848
  const carrier = {};
@@ -2059,6 +2072,36 @@ async function handleInboxInternal(request, parameters, span) {
2059
2072
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
2060
2073
  }
2061
2074
  }
2075
+ if (activity.actorId == null) {
2076
+ logger.error("Missing actor.", {
2077
+ activity: json,
2078
+ recipient
2079
+ });
2080
+ span.setStatus({
2081
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2082
+ message: "Missing actor."
2083
+ });
2084
+ return new Response("Missing actor.", {
2085
+ status: 400,
2086
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2087
+ });
2088
+ }
2089
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
2090
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
2091
+ activity: json,
2092
+ recipient,
2093
+ activityId: activity.id.href,
2094
+ actorIds: activity.actorIds.map((actor) => actor.href)
2095
+ });
2096
+ span.setStatus({
2097
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2098
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
2099
+ });
2100
+ return new Response("The activity and its actors do not have the same origin.", {
2101
+ status: 401,
2102
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2103
+ });
2104
+ }
2062
2105
  const routeResult = await routeActivity({
2063
2106
  context: ctx,
2064
2107
  json,
@@ -3575,6 +3618,33 @@ var FederationImpl = class extends FederationBuilderImpl {
3575
3618
  activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
3576
3619
  span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
3577
3620
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3621
+ if (activity.actorId == null) {
3622
+ logger.error("Missing actor in queued incoming activity.", {
3623
+ activityId: activity.id?.href,
3624
+ activity: message.activity,
3625
+ recipient: message.identifier
3626
+ });
3627
+ span.setStatus({
3628
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3629
+ message: "Missing actor."
3630
+ });
3631
+ span.end();
3632
+ return;
3633
+ }
3634
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
3635
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
3636
+ activityId: activity.id.href,
3637
+ actorIds: activity.actorIds.map((actor) => actor.href),
3638
+ activity: message.activity,
3639
+ recipient: message.identifier
3640
+ });
3641
+ span.setStatus({
3642
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3643
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
3644
+ });
3645
+ span.end();
3646
+ return;
3647
+ }
3578
3648
  cacheKey = activity.id == null ? null : [
3579
3649
  ...this.kvPrefixes.activityIdempotence,
3580
3650
  context.origin,
@@ -4736,20 +4806,6 @@ var ContextImpl = class ContextImpl {
4736
4806
  activity: await fetched.toJsonLd({ contextLoader })
4737
4807
  });
4738
4808
  return false;
4739
- } else if (fetched.actorIds.length < 1) {
4740
- logger.debug("Fetched activity object is missing an actor; unable to verify.", {
4741
- recipient,
4742
- activity: await fetched.toJsonLd({ contextLoader })
4743
- });
4744
- return false;
4745
- }
4746
- const activityId = fetched.id;
4747
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
4748
- logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
4749
- recipient,
4750
- activity: await fetched.toJsonLd({ contextLoader })
4751
- });
4752
- return false;
4753
4809
  }
4754
4810
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
4755
4811
  activity = fetched;
@@ -4758,6 +4814,20 @@ var ContextImpl = class ContextImpl {
4758
4814
  recipient,
4759
4815
  activity: json
4760
4816
  });
4817
+ if (activity.actorId == null) {
4818
+ logger.debug("Activity is missing an actor; unable to verify.", {
4819
+ recipient,
4820
+ activity: json
4821
+ });
4822
+ return false;
4823
+ }
4824
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
4825
+ logger.debug("Activity has actors from different origins; unable to verify.", {
4826
+ recipient,
4827
+ activity: json
4828
+ });
4829
+ return false;
4830
+ }
4761
4831
  const routeResult = await routeActivity({
4762
4832
  context: this,
4763
4833
  json,
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { a as verifyRequestDetailed, d as importJwk, f as validateCryptoKey, i as verifyRequest, m as formatAcceptSignature, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as name, y as version } from "./http-DkzPSl5o.js";
5
+ import { a as verifyRequestDetailed, d as importJwk, f as validateCryptoKey, i as verifyRequest, m as formatAcceptSignature, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as name, y as version } from "./http-OSYYhKM8.js";
6
6
  import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-miAYfiGO.js";
7
- import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-Bz6IdT2p.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DVhfMtqE.js";
7
+ import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-Bm3xyn3D.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-B7X4p1y7.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -781,12 +781,33 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
781
781
  }
782
782
  //#endregion
783
783
  //#region src/federation/inbox.ts
784
+ /**
785
+ * Checks whether an activity's ID and all of its actor IDs have the same
786
+ * non-opaque origin. Activities without an ID have no origin to authenticate.
787
+ *
788
+ * The caller is responsible for rejecting a missing primary actor ID before
789
+ * relying on this check.
790
+ * @internal
791
+ */
792
+ function actorsHaveSameOrigin(activity) {
793
+ if (activity.id == null) return true;
794
+ const origin = activity.id.origin;
795
+ return origin !== "null" && activity.actorIds.every((actor) => actor.origin === origin);
796
+ }
784
797
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
785
798
  const logger = getLogger([
786
799
  "fedify",
787
800
  "federation",
788
801
  "inbox"
789
802
  ]);
803
+ if (activity.actorId == null) {
804
+ logger.error("Missing actor.", { activity: json });
805
+ span.setStatus({
806
+ code: SpanStatusCode.ERROR,
807
+ message: "Missing actor."
808
+ });
809
+ return "missingActor";
810
+ }
790
811
  let cacheKey = null;
791
812
  if (activity.id != null) {
792
813
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
@@ -821,14 +842,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
821
842
  return "alreadyProcessed";
822
843
  }
823
844
  }
824
- if (activity.actorId == null) {
825
- logger.error("Missing actor.", { activity: json });
826
- span.setStatus({
827
- code: SpanStatusCode.ERROR,
828
- message: "Missing actor."
829
- });
830
- return "missingActor";
831
- }
832
845
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
833
846
  if (queue != null) {
834
847
  const carrier = {};
@@ -2058,6 +2071,36 @@ async function handleInboxInternal(request, parameters, span) {
2058
2071
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
2059
2072
  }
2060
2073
  }
2074
+ if (activity.actorId == null) {
2075
+ logger.error("Missing actor.", {
2076
+ activity: json,
2077
+ recipient
2078
+ });
2079
+ span.setStatus({
2080
+ code: SpanStatusCode.ERROR,
2081
+ message: "Missing actor."
2082
+ });
2083
+ return new Response("Missing actor.", {
2084
+ status: 400,
2085
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2086
+ });
2087
+ }
2088
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
2089
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
2090
+ activity: json,
2091
+ recipient,
2092
+ activityId: activity.id.href,
2093
+ actorIds: activity.actorIds.map((actor) => actor.href)
2094
+ });
2095
+ span.setStatus({
2096
+ code: SpanStatusCode.ERROR,
2097
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
2098
+ });
2099
+ return new Response("The activity and its actors do not have the same origin.", {
2100
+ status: 401,
2101
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
2102
+ });
2103
+ }
2061
2104
  const routeResult = await routeActivity({
2062
2105
  context: ctx,
2063
2106
  json,
@@ -3582,6 +3625,33 @@ var FederationImpl = class extends FederationBuilderImpl {
3582
3625
  activity = await Activity.fromJsonLd(parseInput, parseContext);
3583
3626
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
3584
3627
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3628
+ if (activity.actorId == null) {
3629
+ logger.error("Missing actor in queued incoming activity.", {
3630
+ activityId: activity.id?.href,
3631
+ activity: message.activity,
3632
+ recipient: message.identifier
3633
+ });
3634
+ span.setStatus({
3635
+ code: SpanStatusCode.ERROR,
3636
+ message: "Missing actor."
3637
+ });
3638
+ span.end();
3639
+ return;
3640
+ }
3641
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
3642
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
3643
+ activityId: activity.id.href,
3644
+ actorIds: activity.actorIds.map((actor) => actor.href),
3645
+ activity: message.activity,
3646
+ recipient: message.identifier
3647
+ });
3648
+ span.setStatus({
3649
+ code: SpanStatusCode.ERROR,
3650
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
3651
+ });
3652
+ span.end();
3653
+ return;
3654
+ }
3585
3655
  cacheKey = activity.id == null ? null : [
3586
3656
  ...this.kvPrefixes.activityIdempotence,
3587
3657
  context.origin,
@@ -4743,20 +4813,6 @@ var ContextImpl = class ContextImpl {
4743
4813
  activity: await fetched.toJsonLd({ contextLoader })
4744
4814
  });
4745
4815
  return false;
4746
- } else if (fetched.actorIds.length < 1) {
4747
- logger.debug("Fetched activity object is missing an actor; unable to verify.", {
4748
- recipient,
4749
- activity: await fetched.toJsonLd({ contextLoader })
4750
- });
4751
- return false;
4752
- }
4753
- const activityId = fetched.id;
4754
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
4755
- logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
4756
- recipient,
4757
- activity: await fetched.toJsonLd({ contextLoader })
4758
- });
4759
- return false;
4760
4816
  }
4761
4817
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
4762
4818
  activity = fetched;
@@ -4765,6 +4821,20 @@ var ContextImpl = class ContextImpl {
4765
4821
  recipient,
4766
4822
  activity: json
4767
4823
  });
4824
+ if (activity.actorId == null) {
4825
+ logger.debug("Activity is missing an actor; unable to verify.", {
4826
+ recipient,
4827
+ activity: json
4828
+ });
4829
+ return false;
4830
+ }
4831
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
4832
+ logger.debug("Activity has actors from different origins; unable to verify.", {
4833
+ recipient,
4834
+ activity: json
4835
+ });
4836
+ return false;
4837
+ }
4768
4838
  const routeResult = await routeActivity({
4769
4839
  context: this,
4770
4840
  json,
package/dist/mod.cjs CHANGED
@@ -4,11 +4,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("./chunk-DDcVe30Y.cjs");
5
5
  const require_transformers = require("./transformers-NeAONrAq.cjs");
6
6
  require("./compat/mod.cjs");
7
- const require_http = require("./http-C_aA2Zx1.cjs");
8
- const require_middleware = require("./middleware-C7mI18WE.cjs");
7
+ const require_http = require("./http-DEH_eppX.cjs");
8
+ const require_middleware = require("./middleware-QcuVRA-q.cjs");
9
9
  const require_types = require("./types-DRPjmHVV.cjs");
10
- const require_proof = require("./proof-ClpE9zhD.cjs");
11
- const require_kv_cache = require("./kv-cache-Btp4kC_U.cjs");
10
+ const require_proof = require("./proof-BeY7TsiS.cjs");
11
+ const require_kv_cache = require("./kv-cache-CPEV_4nZ.cjs");
12
12
  const require_federation_mod = require("./federation/mod.cjs");
13
13
  require("./nodeinfo/mod.cjs");
14
14
  require("./runtime/mod.cjs");