@fedify/fedify 2.5.0-dev.2446 → 2.5.0-dev.2448
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
- package/dist/{builder-Da8b9O6v.mjs → builder-DbC8bn6k.mjs} +5 -5
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-heeXW2_A.mjs} +2 -2
- package/dist/{context-CQ9H8vNW.d.cts → context-BazwQrHn.d.cts} +304 -4
- package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
- package/dist/{context-BdB54V-A.d.ts → context-GlHrzdSP.d.ts} +304 -4
- package/dist/{deno-eNpfj4oR.mjs → deno-Dpewt_jJ.mjs} +1 -1
- package/dist/{docloader-B-0nqOB1.mjs → docloader-CpsDyqVN.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +5 -5
- package/dist/federation/handler.test.mjs +21 -6
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-actorless.test.d.mts +2 -0
- package/dist/federation/inbox-actorless.test.mjs +487 -0
- package/dist/federation/inbox-authentication.test.d.mts +2 -0
- package/dist/federation/inbox-authentication.test.mjs +116 -0
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox-view.test.d.mts +2 -0
- package/dist/federation/inbox-view.test.mjs +369 -0
- package/dist/federation/inbox.test.mjs +22 -12
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +9 -9
- package/dist/federation/mod.cjs +2 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +8 -2
- package/dist/federation/mq.test.mjs +36 -1
- package/dist/federation/outgoing-proof.test.mjs +4 -4
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-delivery.test.mjs +2 -2
- package/dist/federation/portable-gateway-keys.test.mjs +6 -6
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +3 -3
- package/dist/federation/tasks/tasks.test.mjs +4 -4
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{federation-BVH7drJA.cjs → federation-D8YexkP1.cjs} +1172 -131
- package/dist/{http-DlNxxW_5.js → http-BBr-VLil.js} +143 -16
- package/dist/{http-BUJ5SFh6.mjs → http-BZkyQIkb.mjs} +4 -4
- package/dist/{http-BfW3wyQ4.cjs → http-CAl7Num5.cjs} +161 -16
- package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
- package/dist/inbox-view-NE9wXdLo.mjs +385 -0
- package/dist/inbox-wDuMKaFW.mjs +312 -0
- package/dist/{kv-cache-D_m3A241.mjs → kv-cache-CTQSfkmp.mjs} +1 -1
- package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-CjgCOz3i.cjs} +1 -1
- package/dist/{kv-cache-7015lbrx.js → kv-cache-U7uz5nx4.js} +1 -1
- package/dist/{ld-CD2CsPxq.mjs → ld-CQFuiah9.mjs} +52 -9
- package/dist/ld-CcuZ4NZa.d.ts +250 -0
- package/dist/ld-Cel1RRZ5.d.cts +250 -0
- package/dist/{metrics-D3LHk6Z4.mjs → metrics-B-QxRSSR.mjs} +1 -1
- package/dist/{middleware-CCDXvXOG.mjs → middleware-BdlNa8BD.mjs} +1 -1
- package/dist/{middleware-CzoWvrdZ.mjs → middleware-CuVa0UuS.mjs} +459 -98
- package/dist/{middleware-N9eM16XE.js → middleware-P-AZEQMn.js} +1169 -141
- package/dist/mod-B2783_3v.d.cts +154 -0
- package/dist/mod-BHQE-7WQ.d.ts +154 -0
- package/dist/{mod-D0bxB8jS.d.ts → mod-DjZfJj9R.d.ts} +1 -1
- package/dist/{mod-B0dGgQsg.d.cts → mod-DvWOv2Ho.d.cts} +1 -1
- package/dist/mod.cjs +5 -4
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/{owner-C5-FVX6t.mjs → owner-ccKkQOAf.mjs} +3 -3
- package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CndnpXfo.mjs} +2 -2
- package/dist/{portable-collection-CC2zNAje.js → portable-collection-DUlQ3s2M.js} +1 -1
- package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-TC-kvkVI.cjs} +1 -1
- package/dist/{portable-key-DNc_flH-.mjs → portable-key-D5tFOoV-.mjs} +1 -1
- package/dist/{proof-Cryb1c70.mjs → proof-DPoF06nz.mjs} +98 -10
- package/dist/{send-AF25pWaJ.mjs → send-CF44O1Df.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +1 -1
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +5 -5
- package/dist/sig/proof.test.mjs +44 -4
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.mjs +3 -3
- package/dist/{temporal-BsAeuUJj.mjs → temporal-XlZ4Wb94.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +262 -2
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
- package/dist/inbox-CHvMaIFX.mjs +0 -188
- package/dist/mod-BRpER-B0.d.ts +0 -320
- package/dist/mod-xAfaLS4T.d.cts +0 -320
- package/dist/owner-BF0L9cMt.d.cts +0 -78
- 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-Dpewt_jJ.mjs";
|
|
7
|
+
import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-B-QxRSSR.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 };
|
|
@@ -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-
|
|
4
|
+
import { p as recordDocumentCache } from "./metrics-B-QxRSSR.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,5 +1,5 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
|
-
const require_http = require("./http-
|
|
2
|
+
const require_http = require("./http-CAl7Num5.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");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { X as validateCryptoKey, dt as recordDocumentCache, t as doubleKnock } from "./http-BBr-VLil.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";
|
|
@@ -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-
|
|
7
|
-
import { _ as recordKeyLookup, n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch, t as classifyFetchError } from "./metrics-
|
|
6
|
+
import { n as version, t as name } from "./deno-Dpewt_jJ.mjs";
|
|
7
|
+
import { _ as recordKeyLookup, n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch, t as classifyFetchError } from "./metrics-B-QxRSSR.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";
|
|
@@ -200,6 +200,7 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
|
|
|
200
200
|
actorId: actorId.href,
|
|
201
201
|
error
|
|
202
202
|
});
|
|
203
|
+
if (propagateContextFetchErrors) throw new KeyContextFetchError(error);
|
|
203
204
|
return null;
|
|
204
205
|
}
|
|
205
206
|
let object;
|
|
@@ -1146,8 +1147,26 @@ function createMemoizedDocumentLoader(documentLoader) {
|
|
|
1146
1147
|
return cloneRemoteDocument(await remoteDocument);
|
|
1147
1148
|
};
|
|
1148
1149
|
}
|
|
1150
|
+
const effectiveContextLoaderPolicies = /* @__PURE__ */ new WeakMap();
|
|
1151
|
+
/**
|
|
1152
|
+
* Marks a context loader as the *effective* loader of a policy, i.e., one
|
|
1153
|
+
* that already applies what {@link wrapContextLoaderForJsonLd} (`"standard"`)
|
|
1154
|
+
* or {@link getNormalizationContextLoader} (`"normalization"`) would add.
|
|
1155
|
+
* Those functions return a marked loader as is instead of wrapping it again,
|
|
1156
|
+
* so that a caller can observe every response a parse actually uses,
|
|
1157
|
+
* including Fedify's own baseline contexts.
|
|
1158
|
+
* @param loader The effective context loader.
|
|
1159
|
+
* @param policy The policy the loader already applies.
|
|
1160
|
+
* @returns The given loader.
|
|
1161
|
+
* @internal
|
|
1162
|
+
*/
|
|
1163
|
+
function markEffectiveContextLoader(loader, policy) {
|
|
1164
|
+
effectiveContextLoaderPolicies.set(loader, policy);
|
|
1165
|
+
return loader;
|
|
1166
|
+
}
|
|
1149
1167
|
/** @internal */
|
|
1150
1168
|
function wrapContextLoaderForJsonLd(contextLoader) {
|
|
1169
|
+
if (contextLoader != null && effectiveContextLoaderPolicies.has(contextLoader)) return contextLoader;
|
|
1151
1170
|
const loader = contextLoader ?? builtInContextLoader;
|
|
1152
1171
|
return async (url, options) => {
|
|
1153
1172
|
try {
|
|
@@ -1161,6 +1180,7 @@ function wrapContextLoaderForJsonLd(contextLoader) {
|
|
|
1161
1180
|
}
|
|
1162
1181
|
/** @internal */
|
|
1163
1182
|
function getNormalizationContextLoader(contextLoader) {
|
|
1183
|
+
if (contextLoader != null && effectiveContextLoaderPolicies.get(contextLoader) === "normalization") return contextLoader;
|
|
1164
1184
|
const loader = wrapContextLoaderForJsonLd(contextLoader);
|
|
1165
1185
|
return createMemoizedDocumentLoader(async (url, options) => {
|
|
1166
1186
|
if (URL.canParse(url)) {
|
|
@@ -1613,10 +1633,21 @@ function getLdSignatureObject(jsonLd) {
|
|
|
1613
1633
|
* @returns `true` if the document is authentic; `false` otherwise.
|
|
1614
1634
|
*/
|
|
1615
1635
|
async function verifyJsonLd(jsonLd, options = {}) {
|
|
1616
|
-
return await verifyJsonLdInternal(jsonLd, options, true);
|
|
1636
|
+
return (await verifyJsonLdInternal(jsonLd, options, true)).verified;
|
|
1617
1637
|
}
|
|
1618
1638
|
/** @internal */
|
|
1619
1639
|
async function verifyCompactJsonLd(jsonLd, options = {}) {
|
|
1640
|
+
return (await verifyJsonLdInternal(jsonLd, options, false)).verified;
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Same as {@link verifyCompactJsonLd}, but also returns the key that verified
|
|
1644
|
+
* the signature, whose `owner` {@link fetchKey} has confirmed.
|
|
1645
|
+
* @param jsonLd The compacted JSON-LD document to verify.
|
|
1646
|
+
* @param options Options for verifying the JSON-LD document.
|
|
1647
|
+
* @returns The result.
|
|
1648
|
+
* @internal
|
|
1649
|
+
*/
|
|
1650
|
+
async function verifyCompactJsonLdDetailed(jsonLd, options = {}) {
|
|
1620
1651
|
return await verifyJsonLdInternal(jsonLd, options, false);
|
|
1621
1652
|
}
|
|
1622
1653
|
async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
@@ -1626,7 +1657,7 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
1626
1657
|
}, "linkedData", (observation) => verifyJsonLdInternal(jsonLd, {
|
|
1627
1658
|
...options,
|
|
1628
1659
|
[verificationObservation]: observation
|
|
1629
|
-
}, compact), (value) => value);
|
|
1660
|
+
}, compact), (value) => value.verified);
|
|
1630
1661
|
const observation = options[verificationObservation];
|
|
1631
1662
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
|
|
1632
1663
|
const start = performance.now();
|
|
@@ -1658,12 +1689,18 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
1658
1689
|
const key = await verifySignature(compacted, verificationOptions);
|
|
1659
1690
|
if (key == null) {
|
|
1660
1691
|
if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
|
|
1661
|
-
return
|
|
1692
|
+
return {
|
|
1693
|
+
verified: false,
|
|
1694
|
+
key: null
|
|
1695
|
+
};
|
|
1662
1696
|
}
|
|
1663
1697
|
if (key.ownerId == null) {
|
|
1664
1698
|
if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
|
|
1665
1699
|
logger.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
|
|
1666
|
-
return
|
|
1700
|
+
return {
|
|
1701
|
+
verified: false,
|
|
1702
|
+
key: null
|
|
1703
|
+
};
|
|
1667
1704
|
}
|
|
1668
1705
|
attributions.delete(key.ownerId.href);
|
|
1669
1706
|
if (attributions.size > 0) {
|
|
@@ -1672,10 +1709,16 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
|
|
|
1672
1709
|
attributionIds: [...attributions].map((id) => new URL(id))
|
|
1673
1710
|
};
|
|
1674
1711
|
logger.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
|
|
1675
|
-
return
|
|
1712
|
+
return {
|
|
1713
|
+
verified: false,
|
|
1714
|
+
key: null
|
|
1715
|
+
};
|
|
1676
1716
|
}
|
|
1677
1717
|
verified = true;
|
|
1678
|
-
return
|
|
1718
|
+
return {
|
|
1719
|
+
verified: true,
|
|
1720
|
+
key
|
|
1721
|
+
};
|
|
1679
1722
|
} catch (error) {
|
|
1680
1723
|
threw = true;
|
|
1681
1724
|
span.setStatus({
|
|
@@ -1706,4 +1749,4 @@ async function hashJsonLd(jsonLd, contextLoader) {
|
|
|
1706
1749
|
return encodeHex(await crypto.subtle.digest("SHA-256", encoder.encode(canon)));
|
|
1707
1750
|
}
|
|
1708
1751
|
//#endregion
|
|
1709
|
-
export {
|
|
1752
|
+
export { fetchKey as C, validateCryptoKey as D, importJwk as E, verifyKeyOwnership as O, fetchActorDocument as S, generateCryptoKeyPair as T, verifyJsonLd as _, compactJsonLd as a, bypassKeyCacheReads as b, getNormalizationContextLoader as c, isClearlyMalformedContextReference as d, isInvalidUrlTypeError as f, verifyCompactJsonLdDetailed as g, verifyCompactJsonLd as h, attachSignature as i, hasSignature as l, signJsonLd as m, UnsafeJsonLdError as n, createSignature as o, markEffectiveContextLoader as p, assertSafeJsonLd as r, detachSignature as s, InvalidContextReferenceError as t, hasSignatureLike as u, verifySignature as v, fetchKeyDetailed as w, exportJwk as x, wrapContextLoaderForJsonLd as y };
|