@fedify/fedify 2.3.0-dev.1137 → 2.3.0-dev.1150
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/{builder-BCkBXxky.mjs → builder-Bjm1Jq9n.mjs} +2 -2
- 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/{context-DI2gRbyN.d.cts → context-CRXCkTM6.d.cts} +48 -6
- package/dist/{context-DCtsSHDv.d.ts → context-MgCh7YGu.d.ts} +48 -6
- package/dist/{deno-B_9yJW3w.mjs → deno-CKFE6Uya.mjs} +1 -1
- package/dist/{docloader-BT89tyFr.mjs → docloader-B-ZE1cZf.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +1363 -44
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +60 -1
- package/dist/federation/middleware.test.mjs +1667 -163
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/retry.test.mjs +1 -1
- package/dist/federation/send.test.mjs +8 -8
- package/dist/federation/temporal.test.d.mts +2 -0
- package/dist/federation/temporal.test.mjs +71 -0
- package/dist/federation/webfinger.test.mjs +147 -2
- package/dist/{getMachineId-bsd-etIyxDet.mjs → getMachineId-bsd-BY01PL1n.mjs} +1 -1
- package/dist/{getMachineId-darwin-D23zTf4g.mjs → getMachineId-darwin-Dr1gkBkp.mjs} +1 -1
- package/dist/{getMachineId-win-Dpap6v5i.mjs → getMachineId-win-QEYwcJiy.mjs} +1 -1
- package/dist/{http-CWoeyogl.cjs → http-DQYEA7AZ.cjs} +53 -1
- package/dist/{http-CToqG5ap.js → http-WbS1gKzr.js} +48 -2
- package/dist/{http-Cyx5SNuu.mjs → http-vHCgbhTg.mjs} +3 -3
- package/dist/{key-CkkMJBjF.mjs → key-N0zP_oJA.mjs} +2 -2
- package/dist/{kv-cache-CuCn2xvM.js → kv-cache-DM2O-Yjy.js} +1 -1
- package/dist/{kv-cache-DuEwFYcN.cjs → kv-cache-Dsg_bi4N.cjs} +1 -1
- package/dist/{kv-cache-VHFP42vY.mjs → kv-cache-GXXZEemD.mjs} +1 -1
- package/dist/{ld-k8yqD2a-.mjs → ld-BwKhquPx.mjs} +302 -6
- package/dist/{metrics-iRBg8jTk.mjs → metrics-7Vy9FvEw.mjs} +48 -2
- package/dist/{middleware-D7FrhN9q.js → middleware-BscgvU-m.js} +496 -115
- package/dist/{middleware-BWLUrbS9.cjs → middleware-D_iXrYHJ.cjs} +497 -115
- package/dist/{middleware-CztxpARM.mjs → middleware-Db1_qAFG.mjs} +1 -1
- package/dist/{middleware-DQEgdr83.mjs → middleware-ZuUcO0t1.mjs} +416 -124
- package/dist/{mod-C504qevA.d.cts → mod-C7HOzGqH.d.cts} +11 -2
- package/dist/{mod-wYfuXeDE.d.ts → mod-CpQHB3Ys.d.ts} +11 -2
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +2 -2
- package/dist/mod.d.ts +2 -2
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-nmXdvXpc.mjs → owner-FD0H_vpj.mjs} +2 -2
- package/dist/{proof-CcsIJLTn.cjs → proof-CYK8T8IS.cjs} +353 -3
- package/dist/{proof-NRmtrTDu.js → proof-I3EokKN-.js} +300 -4
- package/dist/{proof-DpwO1T4S.mjs → proof-V_lafPmA.mjs} +3 -3
- package/dist/{send-DvX2tYyZ.mjs → send-Cc2_10tF.mjs} +3 -3
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +558 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/temporal-BkmBfs__.mjs +95 -0
- package/dist/testing/mod.d.mts +48 -6
- 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 +6 -6
- /package/dist/{execAsync-DCBrgFiV.mjs → execAsync-Dxb7rNf3.mjs} +0 -0
- /package/dist/{getMachineId-linux-ObI47Hql.mjs → getMachineId-linux-Bbhofx-s.mjs} +0 -0
- /package/dist/{getMachineId-unsupported-Ddu-PFeh.mjs → getMachineId-unsupported-dIOte2Ct.mjs} +0 -0
- /package/dist/{retry-v_sGLH1d.mjs → retry-_VvV0h9f.mjs} +0 -0
|
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
|
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
import { t as __exportAll } from "./chunk-CRNNMoPX.js";
|
|
4
4
|
import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
|
|
5
|
-
import { C as
|
|
6
|
-
import { c as getKeyOwner,
|
|
5
|
+
import { C as recordWebFingerHandle, O as name, S as recordOutboxEnqueue, a as verifyRequestDetailed, b as recordInboxActivity, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as version, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, w as formatAcceptSignature, x as recordOutboxActivity, y as recordFanoutRecipients } from "./http-WbS1gKzr.js";
|
|
6
|
+
import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-I3EokKN-.js";
|
|
7
7
|
import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DM2O-Yjy.js";
|
|
9
9
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
10
10
|
import { Router, RouterError, assertPath } from "@fedify/uri-template";
|
|
11
11
|
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
@@ -14,6 +14,7 @@ import { encodeHex } from "byte-encodings/hex";
|
|
|
14
14
|
import { uniq } from "es-toolkit";
|
|
15
15
|
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
16
16
|
import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
17
|
+
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
17
18
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
18
19
|
import { domainToASCII } from "node:url";
|
|
19
20
|
//#region src/federation/activity-listener.ts
|
|
@@ -691,7 +692,7 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
|
|
|
691
692
|
}
|
|
692
693
|
//#endregion
|
|
693
694
|
//#region src/federation/inbox.ts
|
|
694
|
-
async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
695
|
+
async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
695
696
|
const logger = getLogger([
|
|
696
697
|
"fedify",
|
|
697
698
|
"federation",
|
|
@@ -750,7 +751,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
|
|
|
750
751
|
type: "inbox",
|
|
751
752
|
id: crypto.randomUUID(),
|
|
752
753
|
baseUrl: ctx.origin,
|
|
753
|
-
activity: json,
|
|
754
|
+
activity: originalJson ?? json,
|
|
755
|
+
...normalizedActivity == null ? {} : { normalizedActivity },
|
|
756
|
+
...ldSignatureVerified == null ? {} : { ldSignatureVerified },
|
|
754
757
|
identifier: recipient,
|
|
755
758
|
attempt: 0,
|
|
756
759
|
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -804,7 +807,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
|
|
|
804
807
|
const activityType = getTypeId(activity).href;
|
|
805
808
|
const started = performance.now();
|
|
806
809
|
try {
|
|
807
|
-
|
|
810
|
+
const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
|
|
811
|
+
await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
|
|
808
812
|
} finally {
|
|
809
813
|
getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
|
|
810
814
|
}
|
|
@@ -1029,7 +1033,123 @@ function acceptsJsonLd(request) {
|
|
|
1029
1033
|
return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
|
|
1030
1034
|
}
|
|
1031
1035
|
//#endregion
|
|
1036
|
+
//#region src/federation/temporal.ts
|
|
1037
|
+
function isPlainObject(value) {
|
|
1038
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
1039
|
+
}
|
|
1040
|
+
function normalizeDateTimeLiteral(value) {
|
|
1041
|
+
return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
|
|
1042
|
+
}
|
|
1043
|
+
function isMalformedDateTimeLiteral(value) {
|
|
1044
|
+
if (typeof value !== "string") return false;
|
|
1045
|
+
try {
|
|
1046
|
+
Temporal.Instant.from(normalizeDateTimeLiteral(value));
|
|
1047
|
+
return false;
|
|
1048
|
+
} catch {
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
function isMalformedDurationLiteral(value) {
|
|
1053
|
+
if (typeof value !== "string") return false;
|
|
1054
|
+
try {
|
|
1055
|
+
Temporal.Duration.from(value);
|
|
1056
|
+
return false;
|
|
1057
|
+
} catch {
|
|
1058
|
+
return true;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
const TEMPORAL_DATE_TIME_IRIS = new Set([
|
|
1062
|
+
"https://www.w3.org/ns/activitystreams#deleted",
|
|
1063
|
+
"https://www.w3.org/ns/activitystreams#endTime",
|
|
1064
|
+
"https://www.w3.org/ns/activitystreams#published",
|
|
1065
|
+
"https://www.w3.org/ns/activitystreams#startTime",
|
|
1066
|
+
"https://www.w3.org/ns/activitystreams#updated",
|
|
1067
|
+
"http://purl.org/dc/terms/created",
|
|
1068
|
+
"https://w3id.org/security#created"
|
|
1069
|
+
]);
|
|
1070
|
+
const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
|
|
1071
|
+
const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
|
|
1072
|
+
const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
|
|
1073
|
+
function hasMalformedExpandedDateTimeLiteral(value) {
|
|
1074
|
+
if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
|
|
1075
|
+
return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
|
|
1076
|
+
}
|
|
1077
|
+
function hasMalformedExpandedQuestionClosedLiteral(value) {
|
|
1078
|
+
if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
|
|
1079
|
+
if (!isPlainObject(value) || !("@value" in value)) return false;
|
|
1080
|
+
const literal = value["@value"];
|
|
1081
|
+
if (typeof literal === "boolean") return false;
|
|
1082
|
+
if (typeof literal !== "string") return false;
|
|
1083
|
+
if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
|
|
1084
|
+
if (new Date(literal).toString() === "Invalid Date") return false;
|
|
1085
|
+
return isMalformedDateTimeLiteral(literal);
|
|
1086
|
+
}
|
|
1087
|
+
function hasMalformedExpandedDurationLiteral(value) {
|
|
1088
|
+
if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
|
|
1089
|
+
return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
|
|
1090
|
+
}
|
|
1091
|
+
function hasMalformedKnownTemporalLiteralInternal(value, visited) {
|
|
1092
|
+
if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
|
|
1093
|
+
if (!isPlainObject(value)) return false;
|
|
1094
|
+
if (visited.has(value)) return false;
|
|
1095
|
+
visited.add(value);
|
|
1096
|
+
if ("@value" in value) return false;
|
|
1097
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1098
|
+
if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
|
|
1099
|
+
if (hasMalformedExpandedDateTimeLiteral(child)) return true;
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
if (key === QUESTION_CLOSED_IRI) {
|
|
1103
|
+
if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (TEMPORAL_DURATION_IRIS.has(key)) {
|
|
1107
|
+
if (hasMalformedExpandedDurationLiteral(child)) return true;
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
|
|
1111
|
+
}
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
|
|
1115
|
+
try {
|
|
1116
|
+
return hasMalformedKnownTemporalLiteralInternal(await jsonld.expand(value, {
|
|
1117
|
+
documentLoader: getNormalizationContextLoader(contextLoader),
|
|
1118
|
+
keepFreeFloatingNodes: true
|
|
1119
|
+
}), /* @__PURE__ */ new Set());
|
|
1120
|
+
} catch {
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
//#endregion
|
|
1032
1125
|
//#region src/federation/handler.ts
|
|
1126
|
+
const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
|
|
1127
|
+
function isRemoteContextLoadingFailure$1(error) {
|
|
1128
|
+
return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
|
|
1129
|
+
}
|
|
1130
|
+
function isPermanentRemoteContextError$1(error) {
|
|
1131
|
+
if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
|
|
1132
|
+
const details = error.details;
|
|
1133
|
+
if (details?.code === "invalid remote context") return true;
|
|
1134
|
+
return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
|
|
1135
|
+
}
|
|
1136
|
+
function isInvalidJsonLdError(error) {
|
|
1137
|
+
if (!(error instanceof Error)) return false;
|
|
1138
|
+
const name = error.name;
|
|
1139
|
+
return name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
|
|
1140
|
+
}
|
|
1141
|
+
function isValidationTypeError(error) {
|
|
1142
|
+
return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
|
|
1143
|
+
}
|
|
1144
|
+
function isPermanentActivityParseError(error) {
|
|
1145
|
+
return isInvalidJsonLdError(error) || isValidationTypeError(error);
|
|
1146
|
+
}
|
|
1147
|
+
function hasHttpSignatureHeaders(request) {
|
|
1148
|
+
return request.headers.has("Signature") || request.headers.has("Signature-Input");
|
|
1149
|
+
}
|
|
1150
|
+
function hasObjectIntegrityProof(json) {
|
|
1151
|
+
return typeof json === "object" && json != null && "proof" in json;
|
|
1152
|
+
}
|
|
1033
1153
|
/**
|
|
1034
1154
|
* Handles an actor request.
|
|
1035
1155
|
* @template TContextData The context data to pass to the context.
|
|
@@ -1101,8 +1221,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
1101
1221
|
* @param parameters The parameters for handling the collection.
|
|
1102
1222
|
* @returns A promise that resolves to an HTTP response.
|
|
1103
1223
|
*/
|
|
1104
|
-
async function handleCollection(request, { name: name$
|
|
1105
|
-
const spanName = name$
|
|
1224
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
|
|
1225
|
+
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
1106
1226
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
1107
1227
|
const tracer = tracerProvider.getTracer(name, version);
|
|
1108
1228
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
@@ -1144,7 +1264,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1144
1264
|
collection = new OrderedCollection({
|
|
1145
1265
|
id: baseUri,
|
|
1146
1266
|
totalItems: totalItems == null ? null : Number(totalItems),
|
|
1147
|
-
items: filterCollectionItems(itemsOrResponse, name$
|
|
1267
|
+
items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
|
|
1148
1268
|
});
|
|
1149
1269
|
} else {
|
|
1150
1270
|
const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
|
|
@@ -1165,7 +1285,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1165
1285
|
} else {
|
|
1166
1286
|
const uri = new URL(baseUri);
|
|
1167
1287
|
uri.searchParams.set("cursor", cursor);
|
|
1168
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
1288
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
1169
1289
|
kind: SpanKind.SERVER,
|
|
1170
1290
|
attributes: {
|
|
1171
1291
|
"activitypub.collection.id": uri.href,
|
|
@@ -1209,7 +1329,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1209
1329
|
id: uri,
|
|
1210
1330
|
prev,
|
|
1211
1331
|
next,
|
|
1212
|
-
items: filterCollectionItems(items, name$
|
|
1332
|
+
items: filterCollectionItems(items, name$1, filterPredicate),
|
|
1213
1333
|
partOf
|
|
1214
1334
|
});
|
|
1215
1335
|
}
|
|
@@ -1556,29 +1676,105 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1556
1676
|
});
|
|
1557
1677
|
}
|
|
1558
1678
|
const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1679
|
+
const jsonWithoutSig = detachSignature(json);
|
|
1680
|
+
const hasLdSignature = hasSignature(json);
|
|
1681
|
+
const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
|
|
1682
|
+
let deferredLdSignatureError = void 0;
|
|
1683
|
+
const respondInvalidActivity = async (error) => {
|
|
1684
|
+
logger.error("Failed to parse activity:\n{error}", {
|
|
1685
|
+
recipient,
|
|
1686
|
+
activity: json,
|
|
1687
|
+
error
|
|
1567
1688
|
});
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1689
|
+
try {
|
|
1690
|
+
await inboxErrorHandler?.(ctx, error);
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
|
1693
|
+
error,
|
|
1694
|
+
activity: json,
|
|
1695
|
+
recipient
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
span.setStatus({
|
|
1699
|
+
code: SpanStatusCode.ERROR,
|
|
1700
|
+
message: `Failed to parse activity:\n${error}`
|
|
1701
|
+
});
|
|
1702
|
+
return new Response("Invalid activity.", {
|
|
1703
|
+
status: 400,
|
|
1704
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
1705
|
+
});
|
|
1706
|
+
};
|
|
1707
|
+
let compactedJson = json;
|
|
1708
|
+
let compactedJsonWithoutSig = jsonWithoutSig;
|
|
1709
|
+
let ldSigVerified = false;
|
|
1710
|
+
if (hasLdSignature) {
|
|
1711
|
+
try {
|
|
1712
|
+
compactedJson = await compactJsonLd(json, ctx.contextLoader);
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
if (isInvalidJsonLdError(error)) {
|
|
1715
|
+
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
1716
|
+
recipient,
|
|
1717
|
+
error
|
|
1718
|
+
});
|
|
1719
|
+
return new Response("Invalid JSON-LD.", {
|
|
1720
|
+
status: 400,
|
|
1721
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
|
|
1725
|
+
if (!skipSignatureVerification) deferredLdSignatureError = error;
|
|
1726
|
+
logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
|
|
1571
1727
|
recipient,
|
|
1572
1728
|
error
|
|
1573
1729
|
});
|
|
1574
|
-
return new Response("Invalid JSON-LD.", {
|
|
1575
|
-
status: 400,
|
|
1576
|
-
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
1577
|
-
});
|
|
1578
1730
|
}
|
|
1579
|
-
|
|
1731
|
+
if (compactedJson !== json) {
|
|
1732
|
+
compactedJsonWithoutSig = detachSignature(compactedJson);
|
|
1733
|
+
try {
|
|
1734
|
+
ldSigVerified = await verifyCompactJsonLd(compactedJson, {
|
|
1735
|
+
contextLoader: ctx.contextLoader,
|
|
1736
|
+
documentLoader: ctx.documentLoader,
|
|
1737
|
+
keyCache,
|
|
1738
|
+
meterProvider,
|
|
1739
|
+
tracerProvider
|
|
1740
|
+
});
|
|
1741
|
+
} catch (error) {
|
|
1742
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
|
|
1743
|
+
if (isInvalidJsonLdError(error)) {
|
|
1744
|
+
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
1745
|
+
recipient,
|
|
1746
|
+
error
|
|
1747
|
+
});
|
|
1748
|
+
return new Response("Invalid JSON-LD.", {
|
|
1749
|
+
status: 400,
|
|
1750
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
|
|
1754
|
+
if (!skipSignatureVerification) try {
|
|
1755
|
+
await Object$1.fromJsonLd(compactedJson, {
|
|
1756
|
+
contextLoader: getNormalizationContextLoader(ctx.contextLoader),
|
|
1757
|
+
documentLoader: ctx.documentLoader,
|
|
1758
|
+
tracerProvider
|
|
1759
|
+
});
|
|
1760
|
+
} catch (parseError) {
|
|
1761
|
+
if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
|
|
1762
|
+
if (isInvalidJsonLdError(parseError)) {
|
|
1763
|
+
logger.error("Failed to parse JSON-LD:\n{error}", {
|
|
1764
|
+
recipient,
|
|
1765
|
+
error: parseError
|
|
1766
|
+
});
|
|
1767
|
+
return new Response("Invalid JSON-LD.", {
|
|
1768
|
+
status: 400,
|
|
1769
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
deferredLdSignatureError = parseError;
|
|
1773
|
+
}
|
|
1774
|
+
ldSigVerified = false;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1580
1777
|
}
|
|
1581
|
-
const jsonWithoutSig = detachSignature(json);
|
|
1582
1778
|
let activity = null;
|
|
1583
1779
|
let activityVerified = false;
|
|
1584
1780
|
if (ldSigVerified) {
|
|
@@ -1586,7 +1782,16 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1586
1782
|
recipient,
|
|
1587
1783
|
json
|
|
1588
1784
|
});
|
|
1589
|
-
|
|
1785
|
+
try {
|
|
1786
|
+
activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
|
|
1787
|
+
...ctx,
|
|
1788
|
+
contextLoader: getNormalizationContextLoader(ctx.contextLoader)
|
|
1789
|
+
});
|
|
1790
|
+
} catch (error) {
|
|
1791
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
|
|
1792
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
1793
|
+
return await respondInvalidActivity(error);
|
|
1794
|
+
}
|
|
1590
1795
|
activityVerified = true;
|
|
1591
1796
|
} else {
|
|
1592
1797
|
logger.debug("Linked Data Signatures are not verified.", {
|
|
@@ -1595,13 +1800,22 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1595
1800
|
});
|
|
1596
1801
|
try {
|
|
1597
1802
|
activity = await verifyObject(Activity, jsonWithoutSig, {
|
|
1598
|
-
contextLoader: ctx.contextLoader,
|
|
1803
|
+
contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
|
|
1599
1804
|
documentLoader: ctx.documentLoader,
|
|
1600
1805
|
keyCache,
|
|
1601
1806
|
meterProvider,
|
|
1602
1807
|
tracerProvider
|
|
1603
1808
|
});
|
|
1604
1809
|
} catch (error) {
|
|
1810
|
+
if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
|
|
1811
|
+
if (deferredLdSignatureError != null) {
|
|
1812
|
+
logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
|
|
1813
|
+
recipient,
|
|
1814
|
+
error
|
|
1815
|
+
});
|
|
1816
|
+
activity = null;
|
|
1817
|
+
}
|
|
1818
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
1605
1819
|
logger.error("Failed to parse activity:\n{error}", {
|
|
1606
1820
|
recipient,
|
|
1607
1821
|
activity: json,
|
|
@@ -1650,6 +1864,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1650
1864
|
tracerProvider
|
|
1651
1865
|
});
|
|
1652
1866
|
if (verification.verified === false) {
|
|
1867
|
+
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
1653
1868
|
const reason = verification.reason;
|
|
1654
1869
|
const remoteHost = "keyId" in reason && reason.keyId != null ? getRemoteHost(reason.keyId) : void 0;
|
|
1655
1870
|
getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure(reason.type, remoteHost);
|
|
@@ -1727,7 +1942,15 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1727
1942
|
}
|
|
1728
1943
|
httpSigKey = verification.key;
|
|
1729
1944
|
}
|
|
1730
|
-
|
|
1945
|
+
try {
|
|
1946
|
+
activity = await Activity.fromJsonLd(jsonWithoutSig, {
|
|
1947
|
+
...ctx,
|
|
1948
|
+
contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
|
|
1949
|
+
});
|
|
1950
|
+
} catch (error) {
|
|
1951
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
1952
|
+
return await respondInvalidActivity(error);
|
|
1953
|
+
}
|
|
1731
1954
|
}
|
|
1732
1955
|
if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
1733
1956
|
span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
|
|
@@ -1739,6 +1962,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1739
1962
|
"http_signatures.key_id": httpSigKey?.id?.href ?? ""
|
|
1740
1963
|
});
|
|
1741
1964
|
if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
|
|
1965
|
+
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
1742
1966
|
getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("actorKeyMismatch", httpSigKey.id == null ? void 0 : getRemoteHost(httpSigKey.id));
|
|
1743
1967
|
logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
|
|
1744
1968
|
activity: json,
|
|
@@ -1765,10 +1989,14 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1765
1989
|
const routeResult = await routeActivity({
|
|
1766
1990
|
context: ctx,
|
|
1767
1991
|
json,
|
|
1992
|
+
originalJson: json,
|
|
1993
|
+
normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
|
|
1994
|
+
ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
|
|
1768
1995
|
activity,
|
|
1769
1996
|
recipient,
|
|
1770
1997
|
inboxListeners,
|
|
1771
1998
|
inboxContextFactory,
|
|
1999
|
+
listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
|
|
1772
2000
|
inboxErrorHandler,
|
|
1773
2001
|
kv,
|
|
1774
2002
|
kvPrefixes,
|
|
@@ -1895,8 +2123,8 @@ var CustomCollectionHandler = class {
|
|
|
1895
2123
|
* @param CollectionPage The CollectionPage constructor.
|
|
1896
2124
|
* @param filterPredicate Optional filter predicate for items.
|
|
1897
2125
|
*/
|
|
1898
|
-
constructor(name$
|
|
1899
|
-
this.name = name$
|
|
2126
|
+
constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
|
|
2127
|
+
this.name = name$2;
|
|
1900
2128
|
this.values = values;
|
|
1901
2129
|
this.context = context;
|
|
1902
2130
|
this.callbacks = callbacks;
|
|
@@ -2601,22 +2829,72 @@ const logger = getLogger([
|
|
|
2601
2829
|
* @returns The response to the request.
|
|
2602
2830
|
*/
|
|
2603
2831
|
async function handleWebFinger(request, options) {
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
});
|
|
2615
|
-
throw error;
|
|
2616
|
-
} finally {
|
|
2617
|
-
span.end();
|
|
2832
|
+
const meterProvider = options.meterProvider;
|
|
2833
|
+
const start = meterProvider == null ? 0 : performance.now();
|
|
2834
|
+
const scheme = computeResourceScheme(options.context.url.searchParams.get("resource"));
|
|
2835
|
+
let notFoundResponse;
|
|
2836
|
+
const wrappedOptions = {
|
|
2837
|
+
...options,
|
|
2838
|
+
async onNotFound(req) {
|
|
2839
|
+
const r = await options.onNotFound(req);
|
|
2840
|
+
notFoundResponse = r;
|
|
2841
|
+
return r;
|
|
2618
2842
|
}
|
|
2619
|
-
}
|
|
2843
|
+
};
|
|
2844
|
+
let response;
|
|
2845
|
+
try {
|
|
2846
|
+
if (options.tracer == null) response = await handleWebFingerInternal(request, wrappedOptions);
|
|
2847
|
+
else response = await options.tracer.startActiveSpan("webfinger.handle", { kind: SpanKind.SERVER }, async (span) => {
|
|
2848
|
+
try {
|
|
2849
|
+
const inner = await handleWebFingerInternal(request, wrappedOptions);
|
|
2850
|
+
span.setStatus({ code: inner.ok ? SpanStatusCode.UNSET : SpanStatusCode.ERROR });
|
|
2851
|
+
return inner;
|
|
2852
|
+
} catch (error) {
|
|
2853
|
+
span.setStatus({
|
|
2854
|
+
code: SpanStatusCode.ERROR,
|
|
2855
|
+
message: String(error)
|
|
2856
|
+
});
|
|
2857
|
+
throw error;
|
|
2858
|
+
} finally {
|
|
2859
|
+
span.end();
|
|
2860
|
+
}
|
|
2861
|
+
});
|
|
2862
|
+
return response;
|
|
2863
|
+
} finally {
|
|
2864
|
+
if (meterProvider != null) recordWebFingerHandle(meterProvider, {
|
|
2865
|
+
durationMs: Math.max(0, performance.now() - start),
|
|
2866
|
+
result: classifyWebFingerHandleResult(response, notFoundResponse),
|
|
2867
|
+
scheme,
|
|
2868
|
+
statusCode: response?.status
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
const WEBFINGER_HANDLE_SCHEME_WHITELIST = new Set([
|
|
2873
|
+
"acct",
|
|
2874
|
+
"http",
|
|
2875
|
+
"https",
|
|
2876
|
+
"mailto"
|
|
2877
|
+
]);
|
|
2878
|
+
function isAllowedResourceScheme(scheme) {
|
|
2879
|
+
return WEBFINGER_HANDLE_SCHEME_WHITELIST.has(scheme);
|
|
2880
|
+
}
|
|
2881
|
+
function computeResourceScheme(resource) {
|
|
2882
|
+
if (resource == null) return void 0;
|
|
2883
|
+
const colon = resource.indexOf(":");
|
|
2884
|
+
if (colon <= 0) return void 0;
|
|
2885
|
+
const candidate = resource.substring(0, colon).toLowerCase();
|
|
2886
|
+
return isAllowedResourceScheme(candidate) ? candidate : "other";
|
|
2887
|
+
}
|
|
2888
|
+
function classifyWebFingerHandleResult(response, notFoundResponse) {
|
|
2889
|
+
if (response == null) return "error";
|
|
2890
|
+
if (notFoundResponse != null && response === notFoundResponse) return "not_found";
|
|
2891
|
+
switch (response.status) {
|
|
2892
|
+
case 200: return "resolved";
|
|
2893
|
+
case 400: return "invalid";
|
|
2894
|
+
case 404: return "not_found";
|
|
2895
|
+
case 410: return "tombstoned";
|
|
2896
|
+
default: return "error";
|
|
2897
|
+
}
|
|
2620
2898
|
}
|
|
2621
2899
|
async function handleWebFingerInternal(request, { context, host, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound, span, webFingerLinksDispatcher }) {
|
|
2622
2900
|
if (actorDispatcher == null) {
|
|
@@ -2732,6 +3010,18 @@ var middleware_exports = /* @__PURE__ */ __exportAll({
|
|
|
2732
3010
|
OutboxContextImpl: () => OutboxContextImpl,
|
|
2733
3011
|
createFederation: () => createFederation
|
|
2734
3012
|
});
|
|
3013
|
+
function isRemoteContextLoadingFailure(error) {
|
|
3014
|
+
return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
|
|
3015
|
+
}
|
|
3016
|
+
function isPermanentRemoteContextError(error) {
|
|
3017
|
+
if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
|
|
3018
|
+
const details = error.details;
|
|
3019
|
+
if (details?.code === "invalid remote context") return true;
|
|
3020
|
+
return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
|
|
3021
|
+
}
|
|
3022
|
+
function isPermanentInboxParseError(error) {
|
|
3023
|
+
return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
|
|
3024
|
+
}
|
|
2735
3025
|
/**
|
|
2736
3026
|
* Create a new {@link Federation} instance.
|
|
2737
3027
|
* @param parameters Parameters for initializing the instance.
|
|
@@ -3234,78 +3524,37 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3234
3524
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
3235
3525
|
if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
|
|
3236
3526
|
}
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
const cacheKey = activity.id == null ? null : [
|
|
3243
|
-
...this.kvPrefixes.activityIdempotence,
|
|
3244
|
-
context.origin,
|
|
3245
|
-
activity.id.href
|
|
3246
|
-
];
|
|
3247
|
-
if (cacheKey != null) {
|
|
3248
|
-
if (await this.kv.get(cacheKey) === true) {
|
|
3249
|
-
logger.debug("Activity {activityId} has already been processed.", {
|
|
3250
|
-
activityId: activity.id?.href,
|
|
3251
|
-
activity: message.activity,
|
|
3252
|
-
recipient: message.identifier
|
|
3253
|
-
});
|
|
3254
|
-
recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
3255
|
-
return;
|
|
3256
|
-
}
|
|
3257
|
-
}
|
|
3258
|
-
await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
|
|
3259
|
-
const dispatched = this.inboxListeners?.dispatchWithClass(activity);
|
|
3260
|
-
if (dispatched == null) {
|
|
3261
|
-
logger.error("Unsupported activity type:\n{activity}", {
|
|
3262
|
-
activityId: activity.id?.href,
|
|
3263
|
-
activity: message.activity,
|
|
3264
|
-
recipient: message.identifier,
|
|
3265
|
-
trial: message.attempt
|
|
3266
|
-
});
|
|
3267
|
-
span.setStatus({
|
|
3268
|
-
code: SpanStatusCode.ERROR,
|
|
3269
|
-
message: `Unsupported activity type: ${activityType}`
|
|
3270
|
-
});
|
|
3271
|
-
recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
3272
|
-
span.end();
|
|
3273
|
-
return;
|
|
3274
|
-
}
|
|
3275
|
-
const { class: cls, listener } = dispatched;
|
|
3276
|
-
span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
|
|
3277
|
-
try {
|
|
3278
|
-
const started = performance.now();
|
|
3279
|
-
try {
|
|
3280
|
-
await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
|
|
3281
|
-
} finally {
|
|
3282
|
-
getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
|
|
3283
|
-
}
|
|
3284
|
-
recordInboxActivity(this.meterProvider, "processed", activityType);
|
|
3285
|
-
} catch (error) {
|
|
3527
|
+
await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (listenerSpan) => {
|
|
3528
|
+
let activity = null;
|
|
3529
|
+
let cacheKey = null;
|
|
3530
|
+
let activityType;
|
|
3531
|
+
const reportInboxError = async (error) => {
|
|
3286
3532
|
try {
|
|
3287
3533
|
await this.inboxErrorHandler?.(context, error);
|
|
3288
3534
|
} catch (error) {
|
|
3289
3535
|
logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
|
|
3290
3536
|
error,
|
|
3291
3537
|
trial: message.attempt,
|
|
3292
|
-
activityId: activity
|
|
3538
|
+
activityId: activity?.id?.href,
|
|
3293
3539
|
activity: message.activity,
|
|
3294
3540
|
recipient: message.identifier
|
|
3295
3541
|
});
|
|
3296
3542
|
}
|
|
3543
|
+
};
|
|
3544
|
+
const handleRetriableFailure = async (error) => {
|
|
3545
|
+
await reportInboxError(error);
|
|
3297
3546
|
if (this.inboxQueue?.nativeRetrial) {
|
|
3298
3547
|
logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
|
|
3299
3548
|
error,
|
|
3300
|
-
activityId: activity
|
|
3549
|
+
activityId: activity?.id?.href,
|
|
3301
3550
|
activity: message.activity,
|
|
3302
3551
|
recipient: message.identifier
|
|
3303
3552
|
});
|
|
3304
|
-
|
|
3553
|
+
listenerSpan.setStatus({
|
|
3305
3554
|
code: SpanStatusCode.ERROR,
|
|
3306
3555
|
message: String(error)
|
|
3307
3556
|
});
|
|
3308
|
-
|
|
3557
|
+
listenerSpan.end();
|
|
3309
3558
|
throw error;
|
|
3310
3559
|
}
|
|
3311
3560
|
const delay = this.inboxRetryPolicy({
|
|
@@ -3316,20 +3565,27 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3316
3565
|
logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
|
|
3317
3566
|
error,
|
|
3318
3567
|
attempt: message.attempt,
|
|
3319
|
-
activityId: activity
|
|
3568
|
+
activityId: activity?.id?.href,
|
|
3320
3569
|
activity: message.activity,
|
|
3321
3570
|
recipient: message.identifier
|
|
3322
3571
|
});
|
|
3572
|
+
if (this.inboxQueue == null) {
|
|
3573
|
+
listenerSpan.setStatus({
|
|
3574
|
+
code: SpanStatusCode.ERROR,
|
|
3575
|
+
message: String(error)
|
|
3576
|
+
});
|
|
3577
|
+
listenerSpan.end();
|
|
3578
|
+
throw error;
|
|
3579
|
+
}
|
|
3323
3580
|
const retryMessage = {
|
|
3324
3581
|
...message,
|
|
3325
3582
|
attempt: message.attempt + 1
|
|
3326
3583
|
};
|
|
3327
|
-
|
|
3328
|
-
if (
|
|
3329
|
-
await inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
|
|
3584
|
+
await this.inboxQueue.enqueue(retryMessage, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
|
|
3585
|
+
if (activityType != null) {
|
|
3330
3586
|
getFederationMetrics(this.meterProvider).recordQueueTaskEnqueued({
|
|
3331
3587
|
role: "inbox",
|
|
3332
|
-
queue: inboxQueue,
|
|
3588
|
+
queue: this.inboxQueue,
|
|
3333
3589
|
activityType
|
|
3334
3590
|
}, retryMessage.attempt);
|
|
3335
3591
|
recordInboxActivity(this.meterProvider, "retried", activityType);
|
|
@@ -3337,26 +3593,137 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3337
3593
|
} else {
|
|
3338
3594
|
logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
|
|
3339
3595
|
error,
|
|
3340
|
-
activityId: activity
|
|
3596
|
+
activityId: activity?.id?.href,
|
|
3341
3597
|
activity: message.activity,
|
|
3342
3598
|
recipient: message.identifier
|
|
3343
3599
|
});
|
|
3344
|
-
recordInboxActivity(this.meterProvider, "abandoned", activityType);
|
|
3600
|
+
if (activityType != null) recordInboxActivity(this.meterProvider, "abandoned", activityType);
|
|
3345
3601
|
}
|
|
3346
|
-
|
|
3602
|
+
listenerSpan.setStatus({
|
|
3347
3603
|
code: SpanStatusCode.ERROR,
|
|
3348
3604
|
message: String(error)
|
|
3349
3605
|
});
|
|
3350
|
-
|
|
3606
|
+
listenerSpan.end();
|
|
3607
|
+
};
|
|
3608
|
+
let dispatched;
|
|
3609
|
+
let parseInput = void 0;
|
|
3610
|
+
let parseContextLoader = context.contextLoader;
|
|
3611
|
+
try {
|
|
3612
|
+
const hasSignatureField = hasSignature(message.activity);
|
|
3613
|
+
const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
|
|
3614
|
+
const parseContext = hasSignatureField ? {
|
|
3615
|
+
...context,
|
|
3616
|
+
contextLoader: getNormalizationContextLoader(context.contextLoader)
|
|
3617
|
+
} : {
|
|
3618
|
+
...context,
|
|
3619
|
+
contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
|
|
3620
|
+
};
|
|
3621
|
+
parseContextLoader = parseContext.contextLoader;
|
|
3622
|
+
let normalizedActivity;
|
|
3623
|
+
if (shouldParseFromNormalizedSignedPayload) {
|
|
3624
|
+
normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
|
|
3625
|
+
assertSafeJsonLd(normalizedActivity);
|
|
3626
|
+
}
|
|
3627
|
+
parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
|
|
3628
|
+
activity = await Activity.fromJsonLd(parseInput, parseContext);
|
|
3629
|
+
activityType = getTypeId(activity).href;
|
|
3630
|
+
span.setAttribute("activitypub.activity.type", activityType);
|
|
3631
|
+
listenerSpan.setAttribute("activitypub.activity.type", activityType);
|
|
3632
|
+
onActivityType?.(activityType);
|
|
3633
|
+
if (activity.id != null) {
|
|
3634
|
+
span.setAttribute("activitypub.activity.id", activity.id.href);
|
|
3635
|
+
listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
|
|
3636
|
+
}
|
|
3637
|
+
cacheKey = activity.id == null ? null : [
|
|
3638
|
+
...this.kvPrefixes.activityIdempotence,
|
|
3639
|
+
context.origin,
|
|
3640
|
+
activity.id.href
|
|
3641
|
+
];
|
|
3642
|
+
if (cacheKey != null) {
|
|
3643
|
+
if (await this.kv.get(cacheKey) === true) {
|
|
3644
|
+
logger.debug("Activity {activityId} has already been processed.", {
|
|
3645
|
+
activityId: activity.id?.href,
|
|
3646
|
+
activity: message.activity,
|
|
3647
|
+
recipient: message.identifier
|
|
3648
|
+
});
|
|
3649
|
+
recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
3650
|
+
listenerSpan.end();
|
|
3651
|
+
return;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
dispatched = this.inboxListeners?.dispatchWithClass(activity);
|
|
3655
|
+
} catch (error) {
|
|
3656
|
+
if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
|
|
3657
|
+
await reportInboxError(error);
|
|
3658
|
+
logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
|
|
3659
|
+
error,
|
|
3660
|
+
trial: message.attempt,
|
|
3661
|
+
activityId: null,
|
|
3662
|
+
activity: message.activity,
|
|
3663
|
+
recipient: message.identifier
|
|
3664
|
+
});
|
|
3665
|
+
listenerSpan.setStatus({
|
|
3666
|
+
code: SpanStatusCode.ERROR,
|
|
3667
|
+
message: String(error)
|
|
3668
|
+
});
|
|
3669
|
+
listenerSpan.end();
|
|
3670
|
+
return;
|
|
3671
|
+
}
|
|
3672
|
+
if (isPermanentInboxParseError(error)) {
|
|
3673
|
+
await reportInboxError(error);
|
|
3674
|
+
logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
|
|
3675
|
+
error,
|
|
3676
|
+
trial: message.attempt,
|
|
3677
|
+
activityId: activity?.id?.href,
|
|
3678
|
+
activity: message.activity,
|
|
3679
|
+
recipient: message.identifier
|
|
3680
|
+
});
|
|
3681
|
+
listenerSpan.setStatus({
|
|
3682
|
+
code: SpanStatusCode.ERROR,
|
|
3683
|
+
message: String(error)
|
|
3684
|
+
});
|
|
3685
|
+
listenerSpan.end();
|
|
3686
|
+
return;
|
|
3687
|
+
}
|
|
3688
|
+
await handleRetriableFailure(error);
|
|
3689
|
+
return;
|
|
3690
|
+
}
|
|
3691
|
+
if (dispatched == null) {
|
|
3692
|
+
logger.error("Unsupported activity type:\n{activity}", {
|
|
3693
|
+
activityId: activity.id?.href,
|
|
3694
|
+
activity: message.activity,
|
|
3695
|
+
recipient: message.identifier,
|
|
3696
|
+
trial: message.attempt
|
|
3697
|
+
});
|
|
3698
|
+
listenerSpan.setStatus({
|
|
3699
|
+
code: SpanStatusCode.ERROR,
|
|
3700
|
+
message: `Unsupported activity type: ${activityType}`
|
|
3701
|
+
});
|
|
3702
|
+
recordInboxActivity(this.meterProvider, "rejected", activityType);
|
|
3703
|
+
listenerSpan.end();
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
const { class: cls, listener } = dispatched;
|
|
3707
|
+
listenerSpan.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
|
|
3708
|
+
try {
|
|
3709
|
+
const started = performance.now();
|
|
3710
|
+
try {
|
|
3711
|
+
await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
|
|
3712
|
+
} finally {
|
|
3713
|
+
getFederationMetrics(this.meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
|
|
3714
|
+
}
|
|
3715
|
+
recordInboxActivity(this.meterProvider, "processed", activityType);
|
|
3716
|
+
} catch (error) {
|
|
3717
|
+
await handleRetriableFailure(error);
|
|
3351
3718
|
return;
|
|
3352
3719
|
}
|
|
3353
3720
|
if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
|
|
3354
3721
|
logger.info("Activity {activityId} has been processed.", {
|
|
3355
|
-
activityId: activity
|
|
3722
|
+
activityId: activity?.id?.href,
|
|
3356
3723
|
activity: message.activity,
|
|
3357
3724
|
recipient: message.identifier
|
|
3358
3725
|
});
|
|
3359
|
-
|
|
3726
|
+
listenerSpan.end();
|
|
3360
3727
|
});
|
|
3361
3728
|
}
|
|
3362
3729
|
startQueue(contextData, options = {}) {
|
|
@@ -3650,7 +4017,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3650
4017
|
actorAliasMapper: this.actorCallbacks?.aliasMapper,
|
|
3651
4018
|
webFingerLinksDispatcher: this.webFingerLinksDispatcher,
|
|
3652
4019
|
onNotFound,
|
|
3653
|
-
tracer
|
|
4020
|
+
tracer,
|
|
4021
|
+
meterProvider: this._meterProvider
|
|
3654
4022
|
});
|
|
3655
4023
|
case "nodeInfoJrd": return await handleNodeInfoJrd(request, context);
|
|
3656
4024
|
case "nodeInfo": return await handleNodeInfo(request, {
|
|
@@ -3736,16 +4104,18 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3736
4104
|
onNotFound
|
|
3737
4105
|
});
|
|
3738
4106
|
context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
|
|
3739
|
-
case "sharedInbox":
|
|
4107
|
+
case "sharedInbox": {
|
|
3740
4108
|
if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
|
|
3741
4109
|
const identity = await this.sharedInboxKeyDispatcher(context);
|
|
3742
4110
|
if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
|
|
3743
4111
|
}
|
|
3744
4112
|
if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
|
|
4113
|
+
const inboxContextFactory = context.toInboxContext.bind(context);
|
|
4114
|
+
inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
|
|
3745
4115
|
return await handleInbox(request, {
|
|
3746
4116
|
recipient: route.values.identifier ?? null,
|
|
3747
4117
|
context,
|
|
3748
|
-
inboxContextFactory
|
|
4118
|
+
inboxContextFactory,
|
|
3749
4119
|
kv: this.kv,
|
|
3750
4120
|
kvPrefixes: this.kvPrefixes,
|
|
3751
4121
|
queue: this.inboxQueue,
|
|
@@ -3761,6 +4131,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3761
4131
|
tracerProvider: this.tracerProvider,
|
|
3762
4132
|
idempotencyStrategy: this.idempotencyStrategy
|
|
3763
4133
|
});
|
|
4134
|
+
}
|
|
3764
4135
|
case "following": return await handleCollection(request, {
|
|
3765
4136
|
name: "following",
|
|
3766
4137
|
identifier: route.values.identifier,
|
|
@@ -4209,6 +4580,7 @@ var ContextImpl = class ContextImpl {
|
|
|
4209
4580
|
...options,
|
|
4210
4581
|
userAgent: options.userAgent ?? this.federation.userAgent,
|
|
4211
4582
|
tracerProvider: options.tracerProvider ?? this.tracerProvider,
|
|
4583
|
+
meterProvider: options.meterProvider ?? this.federation._meterProvider,
|
|
4212
4584
|
allowPrivateAddress: this.federation.allowPrivateAddress
|
|
4213
4585
|
});
|
|
4214
4586
|
}
|
|
@@ -4493,6 +4865,7 @@ var ContextImpl = class ContextImpl {
|
|
|
4493
4865
|
const routeResult = await routeActivity({
|
|
4494
4866
|
context: this,
|
|
4495
4867
|
json,
|
|
4868
|
+
ldSignatureVerified: false,
|
|
4496
4869
|
activity,
|
|
4497
4870
|
recipient,
|
|
4498
4871
|
inboxListeners: this.federation.inboxListeners,
|
|
@@ -4781,6 +5154,14 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
4781
5154
|
}
|
|
4782
5155
|
var InboxContextImpl = class InboxContextImpl extends ContextImpl {
|
|
4783
5156
|
recipient;
|
|
5157
|
+
/**
|
|
5158
|
+
* The original received activity payload.
|
|
5159
|
+
*
|
|
5160
|
+
* Fedify may normalize a Linked Data Signature payload internally for safe
|
|
5161
|
+
* parsing, but forwarding must keep the sender's payload unchanged so
|
|
5162
|
+
* third-party signatures/proofs remain intact.
|
|
5163
|
+
* @internal
|
|
5164
|
+
*/
|
|
4784
5165
|
activity;
|
|
4785
5166
|
activityId;
|
|
4786
5167
|
activityType;
|