@fedify/fedify 2.5.0-dev.2446 → 2.5.0-dev.2492

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 (141) hide show
  1. package/README.md +3 -0
  2. package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
  3. package/dist/{builder-Da8b9O6v.mjs → builder-DphK9v7f.mjs} +5 -5
  4. package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.cts} +0 -1
  5. package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.ts} +0 -1
  6. package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
  7. package/dist/compat/mod.d.cts +1 -1
  8. package/dist/compat/mod.d.ts +1 -1
  9. package/dist/compat/transformers.test.mjs +1 -1
  10. package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
  11. package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
  12. package/dist/{context-BdB54V-A.d.ts → context-DQJE9YfH.d.ts} +332 -6
  13. package/dist/{context-CQ9H8vNW.d.cts → context-DSuL3YWx.d.cts} +332 -6
  14. package/dist/{deno-eNpfj4oR.mjs → deno-iOAA7iRN.mjs} +1 -1
  15. package/dist/docloader-DCZkiTeF.mjs +136 -0
  16. package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
  17. package/dist/federation/builder.test.mjs +1 -1
  18. package/dist/federation/compound-fanout.test.mjs +6 -3
  19. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  20. package/dist/federation/handler.test.mjs +22 -7
  21. package/dist/federation/hashlink-media.test.mjs +2 -2
  22. package/dist/federation/idempotency.test.mjs +2 -2
  23. package/dist/federation/inbox-actorless.test.d.mts +2 -0
  24. package/dist/federation/inbox-actorless.test.mjs +487 -0
  25. package/dist/federation/inbox-authentication.test.d.mts +2 -0
  26. package/dist/federation/inbox-authentication.test.mjs +116 -0
  27. package/dist/federation/inbox-report.test.mjs +5 -5
  28. package/dist/federation/inbox-view.test.d.mts +2 -0
  29. package/dist/federation/inbox-view.test.mjs +369 -0
  30. package/dist/federation/inbox.test.mjs +22 -12
  31. package/dist/federation/metrics.test.mjs +1 -1
  32. package/dist/federation/middleware.test.mjs +470 -35
  33. package/dist/federation/mod.cjs +2 -1
  34. package/dist/federation/mod.d.cts +4 -4
  35. package/dist/federation/mod.d.ts +4 -4
  36. package/dist/federation/mod.js +8 -2
  37. package/dist/federation/mq.test.mjs +36 -1
  38. package/dist/federation/outgoing-proof.test.mjs +9 -6
  39. package/dist/federation/portable-actor.test.mjs +3 -3
  40. package/dist/federation/portable-chain.test.mjs +3 -3
  41. package/dist/federation/portable-collection.test.mjs +4 -4
  42. package/dist/federation/portable-delivery.test.mjs +2 -2
  43. package/dist/federation/portable-gateway-keys.test.mjs +13 -7
  44. package/dist/federation/portable-inbox.test.mjs +4 -4
  45. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  46. package/dist/federation/portable-route-activity.test.mjs +2 -2
  47. package/dist/federation/portable.test.mjs +3 -3
  48. package/dist/federation/send.test.mjs +502 -93
  49. package/dist/federation/tasks/enqueue.test.mjs +3 -3
  50. package/dist/federation/tasks/tasks.test.mjs +4 -4
  51. package/dist/federation/temporal.test.mjs +1 -1
  52. package/dist/federation/webfinger.test.mjs +2 -2
  53. package/dist/{federation-BVH7drJA.cjs → federation-D0AZw_aw.cjs} +1415 -236
  54. package/dist/{http-BUJ5SFh6.mjs → http-BoJCMnjY.mjs} +204 -106
  55. package/dist/{http-DlNxxW_5.js → http-ChXUU_3c.js} +319 -123
  56. package/dist/{http-BfW3wyQ4.cjs → http-kv1LZw-r.cjs} +339 -125
  57. package/dist/inbox-BjihTKLo.mjs +312 -0
  58. package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
  59. package/dist/inbox-view-D-skypg-.mjs +385 -0
  60. package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
  61. package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
  62. package/dist/{kv-cache-D_m3A241.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
  63. package/dist/{kv-cache-7015lbrx.js → kv-cache-DQcte7ES.js} +73 -28
  64. package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
  65. package/dist/ld-CWVpo_NM.d.cts +250 -0
  66. package/dist/{ld-CD2CsPxq.mjs → ld-CdvcOEXQ.mjs} +52 -9
  67. package/dist/ld-Inagww4R.d.ts +250 -0
  68. package/dist/{metrics-D3LHk6Z4.mjs → metrics-CpxvUrBz.mjs} +1 -1
  69. package/dist/{middleware-N9eM16XE.js → middleware-BHb5toqA.js} +1413 -247
  70. package/dist/{middleware-CCDXvXOG.mjs → middleware-BKs2DLUF.mjs} +1 -1
  71. package/dist/{middleware-CzoWvrdZ.mjs → middleware-CoBLrTKX.mjs} +533 -132
  72. package/dist/{mod-B0dGgQsg.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
  73. package/dist/mod-B4kBw1-8.d.ts +154 -0
  74. package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
  75. package/dist/mod-C1nT74IG.d.cts +154 -0
  76. package/dist/{mod-D0bxB8jS.d.ts → mod-CIeBgsOC.d.ts} +1 -1
  77. package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
  78. package/dist/mod.cjs +6 -5
  79. package/dist/mod.d.cts +9 -9
  80. package/dist/mod.d.ts +9 -9
  81. package/dist/mod.js +6 -6
  82. package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
  83. package/dist/nodeinfo/client.test.mjs +337 -19
  84. package/dist/nodeinfo/handler.test.mjs +2 -2
  85. package/dist/nodeinfo/mod.cjs +1 -1
  86. package/dist/nodeinfo/mod.d.cts +1 -1
  87. package/dist/nodeinfo/mod.d.ts +1 -1
  88. package/dist/nodeinfo/mod.js +1 -1
  89. package/dist/otel/exporter.test.mjs +257 -1
  90. package/dist/otel/mod.cjs +36 -30
  91. package/dist/otel/mod.d.cts +13 -0
  92. package/dist/otel/mod.d.ts +13 -0
  93. package/dist/otel/mod.js +36 -30
  94. package/dist/{owner-C5-FVX6t.mjs → owner-DTnVpUKA.mjs} +3 -3
  95. package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CJeYTL61.mjs} +2 -2
  96. package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-DciwBIia.cjs} +1 -1
  97. package/dist/{portable-collection-CC2zNAje.js → portable-collection-TmtpzD4E.js} +1 -1
  98. package/dist/{portable-key-DNc_flH-.mjs → portable-key-DcYok4ki.mjs} +1 -1
  99. package/dist/{proof-Cryb1c70.mjs → proof-GH7Poos_.mjs} +98 -10
  100. package/dist/{send-AF25pWaJ.mjs → send-BgGJh6pv.mjs} +174 -76
  101. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  102. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  103. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  104. package/dist/sig/compound-proof.test.mjs +2 -2
  105. package/dist/sig/http.test.mjs +553 -2
  106. package/dist/sig/key.test.mjs +1 -1
  107. package/dist/sig/ld.test.mjs +1 -1
  108. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  109. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  110. package/dist/sig/mod.cjs +2 -2
  111. package/dist/sig/mod.d.cts +3 -3
  112. package/dist/sig/mod.d.ts +3 -3
  113. package/dist/sig/mod.js +2 -2
  114. package/dist/sig/owner.test.mjs +1 -1
  115. package/dist/sig/portable-collection.test.mjs +1 -1
  116. package/dist/sig/portable-interop.test.mjs +3 -3
  117. package/dist/sig/portable-key.test.mjs +5 -5
  118. package/dist/sig/proof.test.mjs +45 -5
  119. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  120. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  121. package/dist/sig/verification.test.mjs +3 -3
  122. package/dist/{temporal-BsAeuUJj.mjs → temporal-gJINzcL4.mjs} +1 -1
  123. package/dist/testing/kv.test.mjs +1 -1
  124. package/dist/testing/mod.d.mts +288 -2
  125. package/dist/testing/mod.mjs +2 -2
  126. package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
  127. package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
  128. package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
  129. package/dist/utils/docloader.test.mjs +344 -36
  130. package/dist/utils/kv-cache.test.mjs +1 -1
  131. package/dist/utils/mod.cjs +1 -1
  132. package/dist/utils/mod.d.cts +1 -1
  133. package/dist/utils/mod.d.ts +1 -1
  134. package/dist/utils/mod.js +1 -1
  135. package/package.json +7 -7
  136. package/dist/docloader-B-0nqOB1.mjs +0 -91
  137. package/dist/inbox-CHvMaIFX.mjs +0 -188
  138. package/dist/mod-BRpER-B0.d.ts +0 -320
  139. package/dist/mod-xAfaLS4T.d.cts +0 -320
  140. package/dist/owner-BF0L9cMt.d.cts +0 -78
  141. package/dist/owner-BSzr5-I8.d.ts +0 -78
@@ -0,0 +1,312 @@
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 { o as serializeAuthentication, r as getEffectivePrincipals } from "./inbox-authentication-DflPYlLL.mjs";
6
+ import { n as version, t as name } from "./deno-iOAA7iRN.mjs";
7
+ import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-CpxvUrBz.mjs";
8
+ import { getLogger } from "@logtape/logtape";
9
+ import { getTypeId } from "@fedify/vocab";
10
+ import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
11
+ //#region src/federation/inbox.ts
12
+ /**
13
+ * Checks whether an activity's ID and all of its actor IDs have the same
14
+ * [FEP-fe34] origin. Activities without an ID have no origin to authenticate.
15
+ *
16
+ * Origins are compared by `getAuthenticationOrigin()`, so a portable activity
17
+ * and its portable actors, including compatible identifiers on any gateway,
18
+ * are compared by their DIDs. An ID that has no supported origin, such as an
19
+ * opaque URN, never matches.
20
+ *
21
+ * The caller is responsible for rejecting a missing primary actor ID before
22
+ * relying on this check.
23
+ *
24
+ * [FEP-fe34]: https://w3id.org/fep/fe34
25
+ * @internal
26
+ */
27
+ function actorsHaveSameOrigin(activity) {
28
+ if (activity.id == null) return true;
29
+ const origin = getAuthenticationOrigin(activity.id);
30
+ return origin != null && activity.actorIds.every((actor) => getAuthenticationOrigin(actor) === origin);
31
+ }
32
+ /**
33
+ * The headroom that the size check of a queued inbox message leaves for the
34
+ * fields that grow when Fedify retries the message, e.g., its attempt count.
35
+ */
36
+ const QUEUED_MESSAGE_HEADROOM = 64;
37
+ /**
38
+ * Computes the idempotency key of an incoming activity.
39
+ * @returns The key, or `null` if the activity is not deduplicated.
40
+ * @internal
41
+ */
42
+ async function getInboxIdempotencyKey(strategy, inboxContext, activity, origin, recipient, prefix) {
43
+ if (activity.id == null) return null;
44
+ strategy ??= "per-inbox";
45
+ let keyString;
46
+ if (typeof strategy === "function") keyString = await strategy(inboxContext(), activity);
47
+ else switch (strategy) {
48
+ case "global":
49
+ keyString = activity.id.href;
50
+ break;
51
+ case "per-origin":
52
+ keyString = `${origin}\n${activity.id.href}`;
53
+ break;
54
+ case "per-inbox":
55
+ keyString = `${origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
56
+ break;
57
+ default: keyString = `${origin}\n${activity.id.href}`;
58
+ }
59
+ return keyString == null ? null : [...prefix, keyString];
60
+ }
61
+ /**
62
+ * Checks whether an incoming activity has already been processed, by its
63
+ * idempotency key, or by the key that queue workers of older versions of
64
+ * Fedify recorded.
65
+ * @internal
66
+ */
67
+ async function isInboxActivityProcessed(kv, cacheKey, activity, origin, prefix) {
68
+ if (cacheKey == null) return false;
69
+ if (await kv.get(cacheKey) === true) return true;
70
+ if (activity.id == null) return false;
71
+ return await kv.get([
72
+ ...prefix,
73
+ origin,
74
+ activity.id.href
75
+ ]) === true;
76
+ }
77
+ /**
78
+ * The bytes added to the UTF-8 JSON length of a message for each of its JSON
79
+ * values, to bound the size of serialization formats other than JSON, such
80
+ * as the structured serialization of Deno KV, which spends more bytes than
81
+ * JSON on type tags and lengths of small values, e.g., empty arrays, and up
82
+ * to nine bytes on a fractional number that JSON writes in three.
83
+ */
84
+ const PER_VALUE_SERIALIZATION_OVERHEAD = 8;
85
+ /**
86
+ * Estimates, conservatively, how large a queued message is once a queue
87
+ * backend serializes it: the UTF-8 length of its JSON serialization, plus
88
+ * a fixed overhead for each JSON value in it, plus the bytes that encoding
89
+ * strings beyond Latin-1 as UTF-16 adds.
90
+ * @internal
91
+ */
92
+ function estimateQueuedMessageSize(message) {
93
+ const encoder = new TextEncoder();
94
+ let values = 0;
95
+ let stringExpansion = 0;
96
+ const countString = (value) => {
97
+ values++;
98
+ if (!/[\u0100-\uffff]/.test(value)) return;
99
+ const expansion = value.length * 2 - encoder.encode(value).byteLength;
100
+ if (expansion > 0) stringExpansion += expansion;
101
+ };
102
+ const count = (value) => {
103
+ if (typeof value === "string") {
104
+ countString(value);
105
+ return;
106
+ }
107
+ values++;
108
+ if (Array.isArray(value)) for (const item of value) count(item);
109
+ else if (value != null && typeof value === "object") for (const [key, item] of Object.entries(value)) {
110
+ if (item === void 0) continue;
111
+ countString(key);
112
+ count(item);
113
+ }
114
+ };
115
+ count(message);
116
+ return encoder.encode(JSON.stringify(message)).byteLength + stringExpansion + values * PER_VALUE_SERIALIZATION_OVERHEAD;
117
+ }
118
+ /**
119
+ * Checks whether an activity that declares no actor is authenticated: at
120
+ * least one principal must be authenticated for it, and its ID, if any, must
121
+ * have the same [FEP-fe34] origin as one of those principals.
122
+ *
123
+ * [FEP-fe34]: https://w3id.org/fep/fe34
124
+ * @returns `"ok"` if it is authenticated, or why it is not.
125
+ * @internal
126
+ */
127
+ function checkActorlessActivity(activity, authentication) {
128
+ const principals = getEffectivePrincipals(authentication);
129
+ if (principals.length < 1) return {
130
+ status: "noAuthenticatedPrincipal",
131
+ principals
132
+ };
133
+ if (activity.id == null) return {
134
+ status: "ok",
135
+ principals
136
+ };
137
+ const origin = getAuthenticationOrigin(activity.id);
138
+ if (origin == null || !principals.some((principal) => getAuthenticationOrigin(principal.id) === origin)) return {
139
+ status: "activityOriginMismatch",
140
+ principals
141
+ };
142
+ return {
143
+ status: "ok",
144
+ principals
145
+ };
146
+ }
147
+ /**
148
+ * Same as {@link checkActorlessActivity}, but returns a boolean.
149
+ * @internal
150
+ */
151
+ function isActorlessActivityAuthenticated(activity, authentication) {
152
+ return checkActorlessActivity(activity, authentication).status === "ok";
153
+ }
154
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, authentication, verifiedView, replayable, actorless, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
155
+ const logger = getLogger([
156
+ "fedify",
157
+ "federation",
158
+ "inbox"
159
+ ]);
160
+ if (activity.actorId == null && !actorless) {
161
+ logger.error("Missing actor.", { activity: json });
162
+ span.setStatus({
163
+ code: SpanStatusCode.ERROR,
164
+ message: "Missing actor."
165
+ });
166
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
167
+ return "missingActor";
168
+ }
169
+ const contextMetadata = { authentication };
170
+ const cacheKey = await getInboxIdempotencyKey(idempotencyStrategy, () => inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href, contextMetadata), activity, ctx.origin, recipient, kvPrefixes.activityIdempotence);
171
+ if (await isInboxActivityProcessed(kv, cacheKey, activity, ctx.origin, kvPrefixes.activityIdempotence)) {
172
+ logger.debug("Activity {activityId} has already been processed.", {
173
+ activityId: activity.id?.href,
174
+ activity: json,
175
+ recipient
176
+ });
177
+ span.setStatus({
178
+ code: SpanStatusCode.UNSET,
179
+ message: `Activity ${activity.id?.href} has already been processed.`
180
+ });
181
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
182
+ return "alreadyProcessed";
183
+ }
184
+ if (activity.actorId != null) span.setAttribute("activitypub.actor.id", activity.actorId.href);
185
+ let message;
186
+ if (queue != null) {
187
+ const carrier = {};
188
+ propagation.inject(context.active(), carrier);
189
+ message = {
190
+ type: "inbox",
191
+ id: crypto.randomUUID(),
192
+ baseUrl: ctx.origin,
193
+ activity: originalJson ?? json,
194
+ ...normalizedActivity == null ? {} : { normalizedActivity },
195
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
196
+ ...verifiedView == null ? {} : { verifiedView },
197
+ ...authentication == null ? {} : { authentication: serializeAuthentication(authentication) },
198
+ identifier: recipient,
199
+ attempt: 0,
200
+ started: (/* @__PURE__ */ new Date()).toISOString(),
201
+ traceContext: carrier
202
+ };
203
+ const maxMessageSize = queue.maxMessageSize;
204
+ if (replayable === false) {
205
+ logger.warn("The incoming activity {activityId} uses a JSON-LD context that cannot be kept for the queue; processing it immediately instead.", {
206
+ activityId: activity.id?.href,
207
+ recipient
208
+ });
209
+ message = void 0;
210
+ } else if (maxMessageSize != null && estimateQueuedMessageSize(message) > maxMessageSize - QUEUED_MESSAGE_HEADROOM) {
211
+ logger.warn("The incoming activity {activityId} is too large to enqueue; processing it immediately instead.", {
212
+ activityId: activity.id?.href,
213
+ recipient,
214
+ maxMessageSize
215
+ });
216
+ message = void 0;
217
+ }
218
+ }
219
+ if (queue != null && message != null) {
220
+ try {
221
+ await queue.enqueue(message);
222
+ } catch (error) {
223
+ logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
224
+ error,
225
+ activityId: activity.id?.href,
226
+ activity: json,
227
+ recipient
228
+ });
229
+ span.setStatus({
230
+ code: SpanStatusCode.ERROR,
231
+ message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
232
+ });
233
+ throw error;
234
+ }
235
+ getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
236
+ role: "inbox",
237
+ queue,
238
+ activityType: getTypeId(activity).href
239
+ }, 0);
240
+ recordInboxActivity(meterProvider, "queued", getTypeId(activity).href);
241
+ logger.info("Activity {activityId} is enqueued.", {
242
+ activityId: activity.id?.href,
243
+ activity: json,
244
+ recipient
245
+ });
246
+ return "enqueued";
247
+ }
248
+ tracerProvider = tracerProvider ?? trace.getTracerProvider();
249
+ return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
250
+ const dispatched = inboxListeners?.dispatchWithClass(activity);
251
+ if (dispatched == null) {
252
+ logger.error("Unsupported activity type:\n{activity}", {
253
+ activity: json,
254
+ recipient
255
+ });
256
+ span.setStatus({
257
+ code: SpanStatusCode.UNSET,
258
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
259
+ });
260
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
261
+ span.end();
262
+ return "unsupportedActivity";
263
+ }
264
+ const { class: cls, listener } = dispatched;
265
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
266
+ try {
267
+ const activityType = getTypeId(activity).href;
268
+ const started = performance.now();
269
+ try {
270
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
271
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType, contextMetadata), activity);
272
+ } finally {
273
+ getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
274
+ }
275
+ } catch (error) {
276
+ try {
277
+ await inboxErrorHandler?.(ctx, error);
278
+ } catch (error) {
279
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
280
+ error,
281
+ activityId: activity.id?.href,
282
+ activity: json,
283
+ recipient
284
+ });
285
+ }
286
+ logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
287
+ error,
288
+ activityId: activity.id?.href,
289
+ activity: json,
290
+ recipient
291
+ });
292
+ span.setStatus({
293
+ code: SpanStatusCode.ERROR,
294
+ message: String(error)
295
+ });
296
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
297
+ span.end();
298
+ return "error";
299
+ }
300
+ recordInboxActivity(meterProvider, "processed", getTypeId(activity).href);
301
+ if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
302
+ logger.info("Activity {activityId} has been processed.", {
303
+ activityId: activity.id?.href,
304
+ activity: json,
305
+ recipient
306
+ });
307
+ span.end();
308
+ return "success";
309
+ });
310
+ }
311
+ //#endregion
312
+ export { isActorlessActivityAuthenticated as a, getInboxIdempotencyKey as i, checkActorlessActivity as n, isInboxActivityProcessed as o, estimateQueuedMessageSize as r, routeActivity as s, actorsHaveSameOrigin as t };
@@ -0,0 +1,171 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { i as getPortableDid, l as isPortableId, n as getCanonicalPortableId } from "./portable-key-id-Ca30-hOa.mjs";
5
+ //#region src/federation/inbox-authentication.ts
6
+ /**
7
+ * The evidence of an activity that has not been authenticated by Fedify.
8
+ */
9
+ const EMPTY_EVIDENCE = Object.freeze({
10
+ verified: false,
11
+ requestSigners: Object.freeze([]),
12
+ documentSigners: Object.freeze([]),
13
+ originAuthenticated: Object.freeze([]),
14
+ expandedActivity: Object.freeze([]),
15
+ baseUrl: null
16
+ });
17
+ /**
18
+ * Gets the evidence of an activity that has not been authenticated.
19
+ * @internal
20
+ */
21
+ function getEmptyAuthenticationEvidence() {
22
+ return EMPTY_EVIDENCE;
23
+ }
24
+ /**
25
+ * Gets the principals that an authorization decision should rely on.
26
+ * @param evidence The evidence.
27
+ * @param scope See {@link IsActorAuthenticatedOptions.scope}.
28
+ * @returns The principals.
29
+ * @internal
30
+ */
31
+ function getEffectivePrincipals(evidence, scope = "effective") {
32
+ if (scope === "document" || evidence.documentSigners.length > 0) return evidence.documentSigners;
33
+ if (evidence.requestSigners.length > 0) return evidence.requestSigners;
34
+ return evidence.originAuthenticated;
35
+ }
36
+ /**
37
+ * Normalizes an actor ID for comparison: portable IDs and compatible
38
+ * identifiers to their canonical portable ID, other IDs to their `href`.
39
+ * @returns The normalized ID, or `null` for a malformed portable ID.
40
+ */
41
+ function normalizeActorId(id) {
42
+ return isPortableId(id) ? getCanonicalPortableId(id) : id.href;
43
+ }
44
+ /**
45
+ * Checks whether the given principal authenticates the given actor.
46
+ * @param principal The principal.
47
+ * @param actorId The actor ID.
48
+ * @returns `true` if the principal authenticates the actor.
49
+ * @internal
50
+ */
51
+ function principalAuthenticates(principal, actorId) {
52
+ if (principal.kind === "portableAuthority") {
53
+ if (!isPortableId(actorId)) return false;
54
+ const did = getPortableDid(actorId);
55
+ return did != null && did === getPortableDid(principal.id);
56
+ }
57
+ const expected = normalizeActorId(actorId);
58
+ return expected != null && normalizeActorId(principal.id) === expected;
59
+ }
60
+ /**
61
+ * Checks whether the given evidence authenticates the given actor.
62
+ *
63
+ * Actor IDs are compared exactly, except that [FEP-ef61] portable IDs and
64
+ * compatible identifiers are compared by their canonical portable IDs, and
65
+ * a portable authority (a DID) authenticates the portable actors of that DID.
66
+ *
67
+ * @example
68
+ * ``` typescript
69
+ * federation
70
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
71
+ * .on(Follow, async (ctx, follow) => {
72
+ * if (follow.actorId == null) return;
73
+ * // The actor is always authenticated for activities that declare one,
74
+ * // but the same check works for any actor an activity names:
75
+ * isActorAuthenticated(ctx.authentication, follow.actorId);
76
+ * });
77
+ * ```
78
+ *
79
+ * [FEP-ef61]: https://w3id.org/fep/ef61
80
+ * @param evidence The authentication evidence, usually
81
+ * {@link InboxContext.authentication}.
82
+ * @param actorId The actor ID to check.
83
+ * @param options Options.
84
+ * @returns `true` if the actor is authenticated.
85
+ * @since 2.5.0
86
+ */
87
+ function isActorAuthenticated(evidence, actorId, options = {}) {
88
+ return getEffectivePrincipals(evidence, options.scope).some((principal) => principalAuthenticates(principal, actorId));
89
+ }
90
+ function serializePrincipal(principal) {
91
+ return {
92
+ kind: principal.kind,
93
+ id: principal.id.href,
94
+ mechanism: principal.mechanism,
95
+ keyId: principal.keyId?.href ?? null
96
+ };
97
+ }
98
+ /**
99
+ * Serializes the principals of the given evidence for a queued message.
100
+ * @internal
101
+ */
102
+ function serializeAuthentication(evidence) {
103
+ return {
104
+ verified: evidence.verified,
105
+ requestSigners: evidence.requestSigners.map(serializePrincipal),
106
+ documentSigners: evidence.documentSigners.map(serializePrincipal),
107
+ originAuthenticated: evidence.originAuthenticated.map(serializePrincipal)
108
+ };
109
+ }
110
+ const KINDS = /* @__PURE__ */ new Set(["actor", "portableAuthority"]);
111
+ const MECHANISMS = /* @__PURE__ */ new Set([
112
+ "httpSignatures",
113
+ "linkedDataSignatures",
114
+ "objectIntegrityProofs",
115
+ "origin"
116
+ ]);
117
+ function deserializePrincipals(value) {
118
+ if (!Array.isArray(value)) return null;
119
+ const principals = [];
120
+ for (const item of value) {
121
+ if (item == null || typeof item !== "object" || !KINDS.has(item.kind) || !MECHANISMS.has(item.mechanism) || typeof item.id !== "string" || !URL.canParse(item.id) || item.keyId != null && (typeof item.keyId !== "string" || !URL.canParse(item.keyId))) return null;
122
+ principals.push(Object.freeze({
123
+ kind: item.kind,
124
+ id: new URL(item.id),
125
+ mechanism: item.mechanism,
126
+ keyId: item.keyId == null ? null : new URL(item.keyId)
127
+ }));
128
+ }
129
+ return principals;
130
+ }
131
+ /**
132
+ * Restores the evidence of a queued message.
133
+ * @param value The serialized principals.
134
+ * @param expandedActivity The expanded activity, restored from the queued
135
+ * verified view.
136
+ * @param baseUrl The base URL, restored from the queued verified view.
137
+ * @returns The evidence, or `null` if the serialized value is malformed.
138
+ * @internal
139
+ */
140
+ function deserializeAuthentication(value, expandedActivity, baseUrl) {
141
+ if (value == null || typeof value !== "object" || typeof value.verified !== "boolean") return null;
142
+ const record = value;
143
+ const requestSigners = deserializePrincipals(record.requestSigners);
144
+ const documentSigners = deserializePrincipals(record.documentSigners);
145
+ const originAuthenticated = deserializePrincipals(record.originAuthenticated);
146
+ if (requestSigners == null || documentSigners == null || originAuthenticated == null) return null;
147
+ return createAuthenticationEvidence({
148
+ verified: record.verified,
149
+ requestSigners,
150
+ documentSigners,
151
+ originAuthenticated,
152
+ expandedActivity,
153
+ baseUrl
154
+ });
155
+ }
156
+ /**
157
+ * Creates frozen evidence.
158
+ * @internal
159
+ */
160
+ function createAuthenticationEvidence(evidence) {
161
+ return Object.freeze({
162
+ verified: evidence.verified,
163
+ requestSigners: Object.freeze([...evidence.requestSigners]),
164
+ documentSigners: Object.freeze([...evidence.documentSigners]),
165
+ originAuthenticated: Object.freeze([...evidence.originAuthenticated]),
166
+ expandedActivity: evidence.expandedActivity,
167
+ baseUrl: evidence.baseUrl
168
+ });
169
+ }
170
+ //#endregion
171
+ export { isActorAuthenticated as a, getEmptyAuthenticationEvidence as i, deserializeAuthentication as n, serializeAuthentication as o, getEffectivePrincipals as r, createAuthenticationEvidence as t };