@fedify/fedify 2.3.12-dev.2365 → 2.3.13
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/{assert_rejects-DN60FHPX.mjs → assert_rejects-D1HYAik3.mjs} +2 -20
- package/dist/{assert_throws-BOkhLGYc.mjs → assert_throws-BbSMEFKo.mjs} +19 -1
- package/dist/{builder-BOLaWLNy.mjs → builder-BagNDcQw.mjs} +2 -2
- package/dist/compat/transformers.test.mjs +2 -2
- package/dist/{deno-DSS6EkPl.mjs → deno-XpcjbHwj.mjs} +1 -1
- package/dist/{docloader-Ds5k4A6d.mjs → docloader-QDpF5t9M.mjs} +4 -2
- package/dist/federation/builder.test.mjs +3 -3
- package/dist/federation/circuit-breaker.test.mjs +3 -3
- package/dist/federation/collection.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +262 -9
- package/dist/federation/idempotency.test.mjs +3 -3
- package/dist/federation/inbox.test.mjs +25 -1
- package/dist/federation/keycache.test.mjs +1 -1
- package/dist/federation/kv.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +3 -3
- package/dist/federation/middleware.test.mjs +177 -15
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +4 -3
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +1 -1
- package/dist/federation/router.test.mjs +3 -4
- package/dist/federation/send.test.mjs +7 -6
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{http-D9h6mQdq.mjs → http-BRrpIDS1.mjs} +3 -3
- package/dist/{http-DdaLQHim.cjs → http-DwPnG87d.cjs} +109 -11
- package/dist/{http-LMsqGo8j.js → http-GHVGCeb5.js} +110 -12
- package/dist/inbox-BzOuCPLY.mjs +180 -0
- package/dist/{key-vnmAGZ3_.mjs → key-PdEu1lDF.mjs} +111 -13
- package/dist/{kv-cache-Dz7_SbVI.js → kv-cache-BmqdF048.js} +3 -1
- package/dist/{kv-cache-CQuscHxa.mjs → kv-cache-Clx3SwMp.mjs} +1 -1
- package/dist/{kv-cache-ClCz-B0L.cjs → kv-cache-poF6Tqnz.cjs} +3 -1
- package/dist/{ld-BRqKkOSf.mjs → ld-D6UlzNnG.mjs} +3 -3
- package/dist/{metrics-Z0UkrR_1.mjs → metrics-62qolVUY.mjs} +1 -1
- package/dist/{middleware-C_kPmXE6.mjs → middleware-C3Q3CoR8.mjs} +1 -1
- package/dist/{middleware-RTcKhytV.mjs → middleware-CfbFdEO-.mjs} +96 -194
- package/dist/{middleware-DanzltB1.cjs → middleware-DkrXqZO_.cjs} +106 -34
- package/dist/{middleware-B-CIN962.js → middleware-xw9fBZoB.js} +106 -34
- package/dist/mod.cjs +4 -4
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/client.test.mjs +2 -2
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/nodeinfo/types.test.mjs +2 -2
- package/dist/otel/exporter.test.mjs +1 -1
- package/dist/{owner-B0rmuoQB.mjs → owner-DWnLg-vk.mjs} +2 -2
- package/dist/{proof-Bau45ZUZ.js → proof-CUSzBztF.js} +1 -1
- package/dist/{proof-CKczQTG0.cjs → proof-DF6CU-SU.cjs} +1 -1
- package/dist/{proof-B7mAFVed.mjs → proof-mf6h7n4_.mjs} +3 -3
- package/dist/{send-BFukz_kE.mjs → send-LJUzNpVF.mjs} +3 -3
- package/dist/sig/http.test.mjs +6 -6
- package/dist/sig/key.test.mjs +213 -7
- package/dist/sig/ld.test.mjs +5 -5
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +4 -4
- package/dist/sig/proof.test.mjs +5 -4
- package/dist/{std__assert-BvGuO4xO.mjs → std__assert-BjJn2daJ.mjs} +2 -2
- package/dist/{temporal-n6j0MNF9.mjs → temporal-_3yIqdvy.mjs} +1 -1
- package/dist/utils/docloader.test.mjs +114 -39
- 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/{assert-jSdZMYam.mjs → assert-CEO06XCw.mjs} +0 -0
- /package/dist/{circuit-breaker-test-utils-Dm5MM6l9.mjs → circuit-breaker-test-utils-CAdwjgMi.mjs} +0 -0
- /package/dist/{negotiation-CDW-_gUU.mjs → negotiation-DDstyBvc.mjs} +0 -0
- /package/dist/{retry-_VvV0h9f.mjs → retry-CXg_MBI-.mjs} +0 -0
|
@@ -4,13 +4,13 @@ import { getLogger } from "@logtape/logtape";
|
|
|
4
4
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
5
5
|
import { SpanKind, SpanStatusCode, metrics, trace } from "@opentelemetry/api";
|
|
6
6
|
import { encodeHex } from "byte-encodings/hex";
|
|
7
|
-
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
7
|
+
import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
8
8
|
import { Item, decodeDict, encodeDict, encodeItem } from "structured-field-values";
|
|
9
9
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
10
10
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
11
11
|
//#region deno.json
|
|
12
12
|
var name = "@fedify/fedify";
|
|
13
|
-
var version = "2.3.
|
|
13
|
+
var version = "2.3.13";
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/sig/accept.ts
|
|
16
16
|
/**
|
|
@@ -960,6 +960,65 @@ function getDurationMs(start) {
|
|
|
960
960
|
}
|
|
961
961
|
//#endregion
|
|
962
962
|
//#region src/sig/key.ts
|
|
963
|
+
/** Classifies decoding failures without hiding unexpected loader errors. */
|
|
964
|
+
function classifyKeyJsonLdError(error, seen = /* @__PURE__ */ new Set()) {
|
|
965
|
+
if (!(error instanceof Error) || !error.name.startsWith("jsonld.")) return null;
|
|
966
|
+
if (seen.has(error)) throw error;
|
|
967
|
+
seen.add(error);
|
|
968
|
+
const details = error.details;
|
|
969
|
+
if (typeof details?.code !== "string") return null;
|
|
970
|
+
if (details.code !== "loading remote context failed" || !("cause" in details) && !("cause" in error)) return "invalid";
|
|
971
|
+
const cause = "cause" in details ? details.cause : error.cause;
|
|
972
|
+
const nestedFailure = classifyKeyJsonLdError(cause, seen);
|
|
973
|
+
if (nestedFailure != null) return nestedFailure;
|
|
974
|
+
if (cause instanceof SyntaxError) return "invalid";
|
|
975
|
+
if (cause instanceof FetchError || cause instanceof UrlError) return "fetch";
|
|
976
|
+
if (cause instanceof Error && [
|
|
977
|
+
"AbortError",
|
|
978
|
+
"TimeoutError",
|
|
979
|
+
"NetworkError"
|
|
980
|
+
].includes(cause.name)) return "fetch";
|
|
981
|
+
if (cause instanceof TypeError) {
|
|
982
|
+
const urlCode = cause.code;
|
|
983
|
+
if (cause.name === "InvalidContextReferenceError") return "invalid";
|
|
984
|
+
if (urlCode === "ERR_INVALID_URL" || cause.message === "Invalid URL string." || /^Invalid URL(?::|$)/.test(cause.message) || / cannot be parsed as a URL\.?$/.test(cause.message)) return typeof details.url === "string" && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(details.url) && !URL.canParse(details.url) ? "invalid" : "fetch";
|
|
985
|
+
const networkCause = cause.cause;
|
|
986
|
+
const code = cause.code ?? networkCause?.code;
|
|
987
|
+
if (typeof code === "string" && [
|
|
988
|
+
"ECONNREFUSED",
|
|
989
|
+
"ECONNRESET",
|
|
990
|
+
"ECONNABORTED",
|
|
991
|
+
"ETIMEDOUT",
|
|
992
|
+
"EHOSTUNREACH",
|
|
993
|
+
"ENETUNREACH",
|
|
994
|
+
"ENOTFOUND",
|
|
995
|
+
"EAI_AGAIN",
|
|
996
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
997
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
998
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
999
|
+
"UND_ERR_SOCKET"
|
|
1000
|
+
].includes(code) || /^(fetch failed|Failed to fetch|error sending request|error reading a body from connection|Unable to connect|ConnectionRefused|Network connection lost|The socket connection was closed unexpectedly)/.test(cause.message)) return "fetch";
|
|
1001
|
+
}
|
|
1002
|
+
throw cause;
|
|
1003
|
+
}
|
|
1004
|
+
/** Carries a context transport failure through the key decoding boundary. */
|
|
1005
|
+
var KeyContextFetchError = class extends Error {
|
|
1006
|
+
error;
|
|
1007
|
+
constructor(error) {
|
|
1008
|
+
super("Failed to fetch a key's JSON-LD context.", { cause: error });
|
|
1009
|
+
this.error = error;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
function contextFetchError(error) {
|
|
1013
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1014
|
+
while (error instanceof Error && !seen.has(error)) {
|
|
1015
|
+
seen.add(error);
|
|
1016
|
+
const details = error.details;
|
|
1017
|
+
if (!error.name.startsWith("jsonld.") || details?.code !== "loading remote context failed") break;
|
|
1018
|
+
error = "cause" in details ? details.cause : error.cause;
|
|
1019
|
+
}
|
|
1020
|
+
return new KeyContextFetchError(error);
|
|
1021
|
+
}
|
|
963
1022
|
/**
|
|
964
1023
|
* Checks if the given key is valid and supported. No-op if the key is valid,
|
|
965
1024
|
* otherwise throws an error.
|
|
@@ -1054,6 +1113,9 @@ async function importJwk(jwk, type) {
|
|
|
1054
1113
|
* @internal
|
|
1055
1114
|
*/
|
|
1056
1115
|
async function fetchActorDocument(actorId, options = {}) {
|
|
1116
|
+
return await fetchActorDocumentInternal(actorId, options, false);
|
|
1117
|
+
}
|
|
1118
|
+
async function fetchActorDocumentInternal(actorId, options, propagateContextFetchErrors) {
|
|
1057
1119
|
const logger = getLogger([
|
|
1058
1120
|
"fedify",
|
|
1059
1121
|
"sig",
|
|
@@ -1084,9 +1146,12 @@ async function fetchActorDocument(actorId, options = {}) {
|
|
|
1084
1146
|
baseUrl: documentUrl
|
|
1085
1147
|
});
|
|
1086
1148
|
} catch (error) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1149
|
+
const failure = classifyKeyJsonLdError(error);
|
|
1150
|
+
if (failure === "fetch" && propagateContextFetchErrors) throw contextFetchError(error);
|
|
1151
|
+
if (failure == null && !(error instanceof TypeError)) throw error;
|
|
1152
|
+
logger.debug("Failed to decode the actor document at {documentUrl}: {error}", {
|
|
1089
1153
|
documentUrl: documentUrl.href,
|
|
1154
|
+
failure: failure ?? "invalid",
|
|
1090
1155
|
error
|
|
1091
1156
|
});
|
|
1092
1157
|
return null;
|
|
@@ -1125,6 +1190,9 @@ async function fetchActorDocument(actorId, options = {}) {
|
|
|
1125
1190
|
* @internal
|
|
1126
1191
|
*/
|
|
1127
1192
|
async function verifyKeyOwnership(key, options = {}) {
|
|
1193
|
+
return await verifyKeyOwnershipInternal(key, options, false);
|
|
1194
|
+
}
|
|
1195
|
+
async function verifyKeyOwnershipInternal(key, options, propagateContextFetchErrors) {
|
|
1128
1196
|
const logger = getLogger([
|
|
1129
1197
|
"fedify",
|
|
1130
1198
|
"sig",
|
|
@@ -1134,7 +1202,7 @@ async function verifyKeyOwnership(key, options = {}) {
|
|
|
1134
1202
|
if (keyId == null) return null;
|
|
1135
1203
|
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
1136
1204
|
if (claimedOwnerId == null) return null;
|
|
1137
|
-
const owner = await
|
|
1205
|
+
const owner = await fetchActorDocumentInternal(claimedOwnerId, options, propagateContextFetchErrors);
|
|
1138
1206
|
if (owner == null) {
|
|
1139
1207
|
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
1140
1208
|
keyId: keyId.href,
|
|
@@ -1286,6 +1354,21 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
1286
1354
|
tracerProvider
|
|
1287
1355
|
});
|
|
1288
1356
|
} catch (e) {
|
|
1357
|
+
const failure = classifyKeyJsonLdError(e);
|
|
1358
|
+
if (failure === "fetch") throw contextFetchError(e);
|
|
1359
|
+
if (failure != null) {
|
|
1360
|
+
logger.debug("Failed to decode key {keyId}: {error}", {
|
|
1361
|
+
keyId,
|
|
1362
|
+
failure,
|
|
1363
|
+
error: e
|
|
1364
|
+
});
|
|
1365
|
+
await keyCache?.set(cacheKey, null);
|
|
1366
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1367
|
+
return {
|
|
1368
|
+
key: null,
|
|
1369
|
+
cached: false
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1289
1372
|
if (!(e instanceof TypeError)) throw e;
|
|
1290
1373
|
try {
|
|
1291
1374
|
object = await cls.fromJsonLd(document, {
|
|
@@ -1294,8 +1377,14 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
1294
1377
|
tracerProvider
|
|
1295
1378
|
});
|
|
1296
1379
|
} catch (e) {
|
|
1297
|
-
|
|
1298
|
-
|
|
1380
|
+
const failure = classifyKeyJsonLdError(e);
|
|
1381
|
+
if (failure === "fetch") throw contextFetchError(e);
|
|
1382
|
+
if (failure != null || e instanceof TypeError) {
|
|
1383
|
+
logger.debug("Failed to decode key {keyId}: {error}", {
|
|
1384
|
+
keyId,
|
|
1385
|
+
failure: failure ?? "invalid",
|
|
1386
|
+
error: e
|
|
1387
|
+
});
|
|
1299
1388
|
await keyCache?.set(cacheKey, null);
|
|
1300
1389
|
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1301
1390
|
return {
|
|
@@ -1387,16 +1476,17 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
1387
1476
|
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
1388
1477
|
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
1389
1478
|
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
1390
|
-
if (await
|
|
1479
|
+
if (await verifyKeyOwnershipInternal(key, {
|
|
1391
1480
|
documentLoader,
|
|
1392
1481
|
contextLoader,
|
|
1393
1482
|
tracerProvider
|
|
1394
|
-
}) == null) {
|
|
1483
|
+
}, true) == null) {
|
|
1395
1484
|
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
1396
1485
|
keyId,
|
|
1397
1486
|
claimedOwnerId: claimedOwnerId.href
|
|
1398
1487
|
});
|
|
1399
1488
|
await keyCache?.set(cacheKey, null);
|
|
1489
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
1400
1490
|
return {
|
|
1401
1491
|
key: null,
|
|
1402
1492
|
cached: false
|
|
@@ -1447,9 +1537,17 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
1447
1537
|
outcome = classified;
|
|
1448
1538
|
return errored;
|
|
1449
1539
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1540
|
+
try {
|
|
1541
|
+
const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
1542
|
+
outcome = { result: resolved.key != null ? "fetched" : "invalid" };
|
|
1543
|
+
return resolved;
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
if (!(error instanceof KeyContextFetchError)) throw error;
|
|
1546
|
+
const classified = classifyFetchError(error.error);
|
|
1547
|
+
const errored = await onFetchError(error.error, cacheKey, keyId, keyCache, logger);
|
|
1548
|
+
outcome = classified;
|
|
1549
|
+
return errored;
|
|
1550
|
+
}
|
|
1453
1551
|
} finally {
|
|
1454
1552
|
recordKeyLookup(options.meterProvider, {
|
|
1455
1553
|
durationMs: getDurationMs(start),
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { n as version, t as name } from "./deno-XpcjbHwj.mjs";
|
|
5
|
+
import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-62qolVUY.mjs";
|
|
6
|
+
import { getLogger } from "@logtape/logtape";
|
|
7
|
+
import { getTypeId } from "@fedify/vocab";
|
|
8
|
+
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
9
|
+
//#region src/federation/inbox.ts
|
|
10
|
+
/**
|
|
11
|
+
* Checks whether an activity's ID and all of its actor IDs have the same
|
|
12
|
+
* non-opaque origin. Activities without an ID have no origin to authenticate.
|
|
13
|
+
*
|
|
14
|
+
* The caller is responsible for rejecting a missing primary actor ID before
|
|
15
|
+
* relying on this check.
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
function actorsHaveSameOrigin(activity) {
|
|
19
|
+
if (activity.id == null) return true;
|
|
20
|
+
const origin = activity.id.origin;
|
|
21
|
+
return origin !== "null" && activity.actorIds.every((actor) => actor.origin === origin);
|
|
22
|
+
}
|
|
23
|
+
async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
24
|
+
const logger = getLogger([
|
|
25
|
+
"fedify",
|
|
26
|
+
"federation",
|
|
27
|
+
"inbox"
|
|
28
|
+
]);
|
|
29
|
+
if (activity.actorId == null) {
|
|
30
|
+
logger.error("Missing actor.", { activity: json });
|
|
31
|
+
span.setStatus({
|
|
32
|
+
code: SpanStatusCode.ERROR,
|
|
33
|
+
message: "Missing actor."
|
|
34
|
+
});
|
|
35
|
+
recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
|
|
36
|
+
return "missingActor";
|
|
37
|
+
}
|
|
38
|
+
let cacheKey = null;
|
|
39
|
+
if (activity.id != null) {
|
|
40
|
+
const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
|
|
41
|
+
const strategy = idempotencyStrategy ?? "per-inbox";
|
|
42
|
+
let keyString;
|
|
43
|
+
if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
|
|
44
|
+
else switch (strategy) {
|
|
45
|
+
case "global":
|
|
46
|
+
keyString = activity.id.href;
|
|
47
|
+
break;
|
|
48
|
+
case "per-origin":
|
|
49
|
+
keyString = `${ctx.origin}\n${activity.id.href}`;
|
|
50
|
+
break;
|
|
51
|
+
case "per-inbox":
|
|
52
|
+
keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
|
|
53
|
+
break;
|
|
54
|
+
default: keyString = `${ctx.origin}\n${activity.id.href}`;
|
|
55
|
+
}
|
|
56
|
+
if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
|
|
57
|
+
}
|
|
58
|
+
if (cacheKey != null) {
|
|
59
|
+
if (await kv.get(cacheKey) === true) {
|
|
60
|
+
logger.debug("Activity {activityId} has already been processed.", {
|
|
61
|
+
activityId: activity.id?.href,
|
|
62
|
+
activity: json,
|
|
63
|
+
recipient
|
|
64
|
+
});
|
|
65
|
+
span.setStatus({
|
|
66
|
+
code: SpanStatusCode.UNSET,
|
|
67
|
+
message: `Activity ${activity.id?.href} has already been processed.`
|
|
68
|
+
});
|
|
69
|
+
recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
|
|
70
|
+
return "alreadyProcessed";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
span.setAttribute("activitypub.actor.id", activity.actorId.href);
|
|
74
|
+
if (queue != null) {
|
|
75
|
+
const carrier = {};
|
|
76
|
+
propagation.inject(context.active(), carrier);
|
|
77
|
+
try {
|
|
78
|
+
await queue.enqueue({
|
|
79
|
+
type: "inbox",
|
|
80
|
+
id: crypto.randomUUID(),
|
|
81
|
+
baseUrl: ctx.origin,
|
|
82
|
+
activity: originalJson ?? json,
|
|
83
|
+
...normalizedActivity == null ? {} : { normalizedActivity },
|
|
84
|
+
...ldSignatureVerified == null ? {} : { ldSignatureVerified },
|
|
85
|
+
identifier: recipient,
|
|
86
|
+
attempt: 0,
|
|
87
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
88
|
+
traceContext: carrier
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
|
|
92
|
+
error,
|
|
93
|
+
activityId: activity.id?.href,
|
|
94
|
+
activity: json,
|
|
95
|
+
recipient
|
|
96
|
+
});
|
|
97
|
+
span.setStatus({
|
|
98
|
+
code: SpanStatusCode.ERROR,
|
|
99
|
+
message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
|
|
100
|
+
});
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
|
|
104
|
+
role: "inbox",
|
|
105
|
+
queue,
|
|
106
|
+
activityType: getTypeId(activity).href
|
|
107
|
+
}, 0);
|
|
108
|
+
recordInboxActivity(meterProvider, "queued", getTypeId(activity).href);
|
|
109
|
+
logger.info("Activity {activityId} is enqueued.", {
|
|
110
|
+
activityId: activity.id?.href,
|
|
111
|
+
activity: json,
|
|
112
|
+
recipient
|
|
113
|
+
});
|
|
114
|
+
return "enqueued";
|
|
115
|
+
}
|
|
116
|
+
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
117
|
+
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
|
|
118
|
+
const dispatched = inboxListeners?.dispatchWithClass(activity);
|
|
119
|
+
if (dispatched == null) {
|
|
120
|
+
logger.error("Unsupported activity type:\n{activity}", {
|
|
121
|
+
activity: json,
|
|
122
|
+
recipient
|
|
123
|
+
});
|
|
124
|
+
span.setStatus({
|
|
125
|
+
code: SpanStatusCode.UNSET,
|
|
126
|
+
message: `Unsupported activity type: ${getTypeId(activity).href}`
|
|
127
|
+
});
|
|
128
|
+
recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
|
|
129
|
+
span.end();
|
|
130
|
+
return "unsupportedActivity";
|
|
131
|
+
}
|
|
132
|
+
const { class: cls, listener } = dispatched;
|
|
133
|
+
span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
|
|
134
|
+
try {
|
|
135
|
+
const activityType = getTypeId(activity).href;
|
|
136
|
+
const started = performance.now();
|
|
137
|
+
try {
|
|
138
|
+
const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
|
|
139
|
+
await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
|
|
140
|
+
} finally {
|
|
141
|
+
getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
try {
|
|
145
|
+
await inboxErrorHandler?.(ctx, error);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
|
148
|
+
error,
|
|
149
|
+
activityId: activity.id?.href,
|
|
150
|
+
activity: json,
|
|
151
|
+
recipient
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
|
|
155
|
+
error,
|
|
156
|
+
activityId: activity.id?.href,
|
|
157
|
+
activity: json,
|
|
158
|
+
recipient
|
|
159
|
+
});
|
|
160
|
+
span.setStatus({
|
|
161
|
+
code: SpanStatusCode.ERROR,
|
|
162
|
+
message: String(error)
|
|
163
|
+
});
|
|
164
|
+
recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
|
|
165
|
+
span.end();
|
|
166
|
+
return "error";
|
|
167
|
+
}
|
|
168
|
+
recordInboxActivity(meterProvider, "processed", getTypeId(activity).href);
|
|
169
|
+
if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
|
|
170
|
+
logger.info("Activity {activityId} has been processed.", {
|
|
171
|
+
activityId: activity.id?.href,
|
|
172
|
+
activity: json,
|
|
173
|
+
recipient
|
|
174
|
+
});
|
|
175
|
+
span.end();
|
|
176
|
+
return "success";
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
export { routeActivity as n, actorsHaveSameOrigin as t };
|
|
@@ -1,13 +1,72 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-XpcjbHwj.mjs";
|
|
5
|
+
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-62qolVUY.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
|
-
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
9
|
+
import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
10
10
|
//#region src/sig/key.ts
|
|
11
|
+
/** Classifies decoding failures without hiding unexpected loader errors. */
|
|
12
|
+
function classifyKeyJsonLdError(error, seen = /* @__PURE__ */ new Set()) {
|
|
13
|
+
if (!(error instanceof Error) || !error.name.startsWith("jsonld.")) return null;
|
|
14
|
+
if (seen.has(error)) throw error;
|
|
15
|
+
seen.add(error);
|
|
16
|
+
const details = error.details;
|
|
17
|
+
if (typeof details?.code !== "string") return null;
|
|
18
|
+
if (details.code !== "loading remote context failed" || !("cause" in details) && !("cause" in error)) return "invalid";
|
|
19
|
+
const cause = "cause" in details ? details.cause : error.cause;
|
|
20
|
+
const nestedFailure = classifyKeyJsonLdError(cause, seen);
|
|
21
|
+
if (nestedFailure != null) return nestedFailure;
|
|
22
|
+
if (cause instanceof SyntaxError) return "invalid";
|
|
23
|
+
if (cause instanceof FetchError || cause instanceof UrlError) return "fetch";
|
|
24
|
+
if (cause instanceof Error && [
|
|
25
|
+
"AbortError",
|
|
26
|
+
"TimeoutError",
|
|
27
|
+
"NetworkError"
|
|
28
|
+
].includes(cause.name)) return "fetch";
|
|
29
|
+
if (cause instanceof TypeError) {
|
|
30
|
+
const urlCode = cause.code;
|
|
31
|
+
if (cause.name === "InvalidContextReferenceError") return "invalid";
|
|
32
|
+
if (urlCode === "ERR_INVALID_URL" || cause.message === "Invalid URL string." || /^Invalid URL(?::|$)/.test(cause.message) || / cannot be parsed as a URL\.?$/.test(cause.message)) return typeof details.url === "string" && /^[A-Za-z][A-Za-z0-9+.-]*:/.test(details.url) && !URL.canParse(details.url) ? "invalid" : "fetch";
|
|
33
|
+
const networkCause = cause.cause;
|
|
34
|
+
const code = cause.code ?? networkCause?.code;
|
|
35
|
+
if (typeof code === "string" && [
|
|
36
|
+
"ECONNREFUSED",
|
|
37
|
+
"ECONNRESET",
|
|
38
|
+
"ECONNABORTED",
|
|
39
|
+
"ETIMEDOUT",
|
|
40
|
+
"EHOSTUNREACH",
|
|
41
|
+
"ENETUNREACH",
|
|
42
|
+
"ENOTFOUND",
|
|
43
|
+
"EAI_AGAIN",
|
|
44
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
45
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
46
|
+
"UND_ERR_BODY_TIMEOUT",
|
|
47
|
+
"UND_ERR_SOCKET"
|
|
48
|
+
].includes(code) || /^(fetch failed|Failed to fetch|error sending request|error reading a body from connection|Unable to connect|ConnectionRefused|Network connection lost|The socket connection was closed unexpectedly)/.test(cause.message)) return "fetch";
|
|
49
|
+
}
|
|
50
|
+
throw cause;
|
|
51
|
+
}
|
|
52
|
+
/** Carries a context transport failure through the key decoding boundary. */
|
|
53
|
+
var KeyContextFetchError = class extends Error {
|
|
54
|
+
error;
|
|
55
|
+
constructor(error) {
|
|
56
|
+
super("Failed to fetch a key's JSON-LD context.", { cause: error });
|
|
57
|
+
this.error = error;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
function contextFetchError(error) {
|
|
61
|
+
const seen = /* @__PURE__ */ new Set();
|
|
62
|
+
while (error instanceof Error && !seen.has(error)) {
|
|
63
|
+
seen.add(error);
|
|
64
|
+
const details = error.details;
|
|
65
|
+
if (!error.name.startsWith("jsonld.") || details?.code !== "loading remote context failed") break;
|
|
66
|
+
error = "cause" in details ? details.cause : error.cause;
|
|
67
|
+
}
|
|
68
|
+
return new KeyContextFetchError(error);
|
|
69
|
+
}
|
|
11
70
|
/**
|
|
12
71
|
* Checks if the given key is valid and supported. No-op if the key is valid,
|
|
13
72
|
* otherwise throws an error.
|
|
@@ -102,6 +161,9 @@ async function importJwk(jwk, type) {
|
|
|
102
161
|
* @internal
|
|
103
162
|
*/
|
|
104
163
|
async function fetchActorDocument(actorId, options = {}) {
|
|
164
|
+
return await fetchActorDocumentInternal(actorId, options, false);
|
|
165
|
+
}
|
|
166
|
+
async function fetchActorDocumentInternal(actorId, options, propagateContextFetchErrors) {
|
|
105
167
|
const logger = getLogger([
|
|
106
168
|
"fedify",
|
|
107
169
|
"sig",
|
|
@@ -132,9 +194,12 @@ async function fetchActorDocument(actorId, options = {}) {
|
|
|
132
194
|
baseUrl: documentUrl
|
|
133
195
|
});
|
|
134
196
|
} catch (error) {
|
|
135
|
-
|
|
136
|
-
|
|
197
|
+
const failure = classifyKeyJsonLdError(error);
|
|
198
|
+
if (failure === "fetch" && propagateContextFetchErrors) throw contextFetchError(error);
|
|
199
|
+
if (failure == null && !(error instanceof TypeError)) throw error;
|
|
200
|
+
logger.debug("Failed to decode the actor document at {documentUrl}: {error}", {
|
|
137
201
|
documentUrl: documentUrl.href,
|
|
202
|
+
failure: failure ?? "invalid",
|
|
138
203
|
error
|
|
139
204
|
});
|
|
140
205
|
return null;
|
|
@@ -173,6 +238,9 @@ async function fetchActorDocument(actorId, options = {}) {
|
|
|
173
238
|
* @internal
|
|
174
239
|
*/
|
|
175
240
|
async function verifyKeyOwnership(key, options = {}) {
|
|
241
|
+
return await verifyKeyOwnershipInternal(key, options, false);
|
|
242
|
+
}
|
|
243
|
+
async function verifyKeyOwnershipInternal(key, options, propagateContextFetchErrors) {
|
|
176
244
|
const logger = getLogger([
|
|
177
245
|
"fedify",
|
|
178
246
|
"sig",
|
|
@@ -182,7 +250,7 @@ async function verifyKeyOwnership(key, options = {}) {
|
|
|
182
250
|
if (keyId == null) return null;
|
|
183
251
|
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
184
252
|
if (claimedOwnerId == null) return null;
|
|
185
|
-
const owner = await
|
|
253
|
+
const owner = await fetchActorDocumentInternal(claimedOwnerId, options, propagateContextFetchErrors);
|
|
186
254
|
if (owner == null) {
|
|
187
255
|
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
188
256
|
keyId: keyId.href,
|
|
@@ -334,6 +402,21 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
334
402
|
tracerProvider
|
|
335
403
|
});
|
|
336
404
|
} catch (e) {
|
|
405
|
+
const failure = classifyKeyJsonLdError(e);
|
|
406
|
+
if (failure === "fetch") throw contextFetchError(e);
|
|
407
|
+
if (failure != null) {
|
|
408
|
+
logger.debug("Failed to decode key {keyId}: {error}", {
|
|
409
|
+
keyId,
|
|
410
|
+
failure,
|
|
411
|
+
error: e
|
|
412
|
+
});
|
|
413
|
+
await keyCache?.set(cacheKey, null);
|
|
414
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
415
|
+
return {
|
|
416
|
+
key: null,
|
|
417
|
+
cached: false
|
|
418
|
+
};
|
|
419
|
+
}
|
|
337
420
|
if (!(e instanceof TypeError)) throw e;
|
|
338
421
|
try {
|
|
339
422
|
object = await cls.fromJsonLd(document, {
|
|
@@ -342,8 +425,14 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
342
425
|
tracerProvider
|
|
343
426
|
});
|
|
344
427
|
} catch (e) {
|
|
345
|
-
|
|
346
|
-
|
|
428
|
+
const failure = classifyKeyJsonLdError(e);
|
|
429
|
+
if (failure === "fetch") throw contextFetchError(e);
|
|
430
|
+
if (failure != null || e instanceof TypeError) {
|
|
431
|
+
logger.debug("Failed to decode key {keyId}: {error}", {
|
|
432
|
+
keyId,
|
|
433
|
+
failure: failure ?? "invalid",
|
|
434
|
+
error: e
|
|
435
|
+
});
|
|
347
436
|
await keyCache?.set(cacheKey, null);
|
|
348
437
|
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
349
438
|
return {
|
|
@@ -435,16 +524,17 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
435
524
|
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
436
525
|
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
437
526
|
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
438
|
-
if (await
|
|
527
|
+
if (await verifyKeyOwnershipInternal(key, {
|
|
439
528
|
documentLoader,
|
|
440
529
|
contextLoader,
|
|
441
530
|
tracerProvider
|
|
442
|
-
}) == null) {
|
|
531
|
+
}, true) == null) {
|
|
443
532
|
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
444
533
|
keyId,
|
|
445
534
|
claimedOwnerId: claimedOwnerId.href
|
|
446
535
|
});
|
|
447
536
|
await keyCache?.set(cacheKey, null);
|
|
537
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
448
538
|
return {
|
|
449
539
|
key: null,
|
|
450
540
|
cached: false
|
|
@@ -495,9 +585,17 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
495
585
|
outcome = classified;
|
|
496
586
|
return errored;
|
|
497
587
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
588
|
+
try {
|
|
589
|
+
const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
590
|
+
outcome = { result: resolved.key != null ? "fetched" : "invalid" };
|
|
591
|
+
return resolved;
|
|
592
|
+
} catch (error) {
|
|
593
|
+
if (!(error instanceof KeyContextFetchError)) throw error;
|
|
594
|
+
const classified = classifyFetchError(error.error);
|
|
595
|
+
const errored = await onFetchError(error.error, cacheKey, keyId, keyCache, logger);
|
|
596
|
+
outcome = classified;
|
|
597
|
+
return errored;
|
|
598
|
+
}
|
|
501
599
|
} finally {
|
|
502
600
|
recordKeyLookup(options.meterProvider, {
|
|
503
601
|
durationMs: getDurationMs(start),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-
|
|
3
|
+
import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-GHVGCeb5.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
6
|
import { curry } from "es-toolkit";
|
|
@@ -57,6 +57,8 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
async function validateUrl(url) {
|
|
60
|
+
const protocol = new URL(url).protocol;
|
|
61
|
+
if (protocol !== "http:" && protocol !== "https:") throw new UrlError(`Unsupported protocol: ${protocol}`);
|
|
60
62
|
if (!allowPrivateAddress) try {
|
|
61
63
|
await validatePublicUrl(url);
|
|
62
64
|
} catch (error) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "@js-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-62qolVUY.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,7 +1,7 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
require("./chunk-DDcVe30Y.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-DwPnG87d.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
6
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
7
7
|
let es_toolkit = require("es-toolkit");
|
|
@@ -58,6 +58,8 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedi
|
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
async function validateUrl(url) {
|
|
61
|
+
const protocol = new URL(url).protocol;
|
|
62
|
+
if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
|
|
61
63
|
if (!allowPrivateAddress) try {
|
|
62
64
|
await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
|
|
63
65
|
} catch (error) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import { r as fetchKey, s as validateCryptoKey } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-XpcjbHwj.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-62qolVUY.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-PdEu1lDF.mjs";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
8
8
|
import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
|
|
9
9
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
4
|
+
import { n as version, t as name } from "./deno-XpcjbHwj.mjs";
|
|
5
5
|
import { metrics } from "@opentelemetry/api";
|
|
6
6
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/federation/metrics.ts
|