@fedify/fedify 2.4.0-dev.1964 → 2.4.0-dev.1995
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-B4zpKIFU.mjs → builder-D0DxgOV5.mjs} +2 -2
- package/dist/{client-CtxvmazV.mjs → client-DpnpgjyX.mjs} +78 -4
- 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-CJnWc6c-.mjs +495 -0
- package/dist/{context-COLwHogJ.d.cts → context-BeDnDuF3.d.cts} +4 -3
- package/dist/{context-DiiCt4S7.d.ts → context-uyRUWoZQ.d.ts} +4 -3
- package/dist/{deno-CtG93ohj.mjs → deno-CABhzCb_.mjs} +1 -1
- package/dist/{docloader-dk_k3S8d.mjs → docloader-BbGSL0Xq.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +152 -0
- package/dist/federation/handler.test.mjs +122 -4
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +52 -9
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +111 -15
- 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/send.test.mjs +139 -4
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-Drjlrrax.cjs → federation-DCEHQk4G.cjs} +632 -57
- package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
- package/dist/{http-Dj4s1UQ0.js → http-9WyuldYh.js} +170 -8
- package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
- package/dist/{http-B3skRvGo.cjs → http-CRFns-UE.cjs} +181 -7
- package/dist/{http-D7LX-BL9.mjs → http-D9urSO2D.mjs} +3 -3
- package/dist/{key-BVjgbs_9.mjs → key-Ccs8cnd7.mjs} +169 -7
- package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
- package/dist/{kv-cache-DtsjH_bY.cjs → kv-cache-DXT9vmIg.cjs} +2 -2
- package/dist/{kv-cache-tGNbvDCF.js → kv-cache-DtmXhPXG.js} +2 -2
- package/dist/{kv-cache-xRcC_xxP.mjs → kv-cache-lKyFn_Dn.mjs} +1 -1
- package/dist/{ld-OuCeQFny.mjs → ld-qup__MAW.mjs} +3 -3
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-B9N27cCB.mjs → metrics-CISXCvB1.mjs} +1 -1
- package/dist/{middleware-TeLjzoxn.mjs → middleware-CzvJvPHK.mjs} +58 -15
- package/dist/{middleware-Cd_vPqDH.mjs → middleware-DkrU2WhY.mjs} +1 -1
- package/dist/{middleware-CPTDFMmJ.js → middleware-DsfWajaD.js} +593 -18
- package/dist/{mod-DypxPS9H.d.ts → mod-C0BYc0OU.d.ts} +1 -1
- package/dist/{mod-CnGLNoVw.d.cts → mod-CbKDOnCt.d.cts} +1 -1
- package/dist/{mod-CLHkCwLm.d.cts → mod-CyiSjRkk.d.cts} +1 -1
- package/dist/{mod-CSH1Ee-I.d.ts → mod-D882_Xsg.d.ts} +1 -1
- package/dist/{mod-DzRXACM5.d.ts → mod-UDwMjuHM.d.ts} +1 -1
- package/dist/{mod-BZldCEU0.d.cts → mod-x9W0d0Pk.d.cts} +1 -1
- package/dist/mod.cjs +20 -20
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-DGSfntI-.d.ts → owner-BF0L9cMt.d.cts} +12 -1
- package/dist/{proof-BO_esQii.js → owner-BIPEZGiU.js} +1255 -1098
- package/dist/{owner-r_waTiQj.d.cts → owner-BSzr5-I8.d.ts} +12 -1
- package/dist/owner-CRoq8Spb.mjs +175 -0
- package/dist/{proof-Dua0d906.cjs → owner-CVA7L9MA.cjs} +1266 -1097
- package/dist/{proof-D5Ajk4DJ.mjs → proof-BDxNVZ3E.mjs} +186 -83
- package/dist/{send-DVXyUqhO.mjs → send-BzM5GLyn.mjs} +68 -11
- package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
- package/dist/sig/compound-proof-discovery.test.mjs +331 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +465 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +324 -0
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +169 -2
- package/dist/sig/ld.test.mjs +55 -2
- package/dist/sig/mod.cjs +17 -17
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +103 -4
- package/dist/sig/proof.test.mjs +120 -1
- package/dist/{temporal-BoDw51A-.mjs → temporal-Dv6Qv-8k.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +2 -1
- package/dist/{types-RXm5YxpC.cjs → types-Be2zbyeG.cjs} +94 -2
- package/dist/{types-Dtyf8lVj.js → types-C_cNlNVK.js} +78 -4
- package/dist/utils/docloader.test.mjs +27 -3
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
- package/dist/owner-DTEqTzSr.mjs +0 -120
|
@@ -2,19 +2,19 @@ import { Temporal } from "temporal-polyfill";
|
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
import { t as __exportAll } from "./rolldown-runtime-BC-Eo5Mj.js";
|
|
4
4
|
import { r as getDefaultActivityTransformers } from "./transformers-DAtXl_Kc.js";
|
|
5
|
-
import { A as
|
|
6
|
-
import {
|
|
7
|
-
import { n as
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
5
|
+
import { A as recordWebFingerHandle, C as recordCollectionRequest, D as recordInboxActivity, E as recordFanoutRecipients, I as name, L as version, M as formatAcceptSignature, O as recordOutboxActivity, S as recordCollectionPageItems, _ as instrumentDocumentLoader, a as verifyRequestDetailed, b as recordCircuitBreakerStateChange, d as importJwk, f as validateCryptoKey, g as getRemoteHost, h as getFederationMetrics, i as verifyRequest, j as registerQueueDepthGauge, k as recordOutboxEnqueue, m as getDurationMs, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as isAbortError, w as recordCollectionTotalItems, x as recordCollectionDispatchDuration } from "./http-9WyuldYh.js";
|
|
6
|
+
import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-C_cNlNVK.js";
|
|
7
|
+
import { C as verifyCompactJsonLd, D as normalizeOutgoingActivityJsonLd, E as wrapContextLoaderForJsonLd, S as signJsonLd, _ as getNormalizationContextLoader, a as signObject, b as isClearlyMalformedContextReference, d as InvalidContextReferenceError, f as assertSafeJsonLd, g as detachSignature, i as hasProofLike, l as verifyPortableObjectProofPolicy, m as compactJsonLd, n as getKeyOwner, o as verifyMapLocalProof, s as verifyObject, t as doesActorOwnKey, v as hasSignature, x as isInvalidUrlTypeError, y as hasSignatureLike } from "./owner-BIPEZGiU.js";
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DtmXhPXG.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";
|
|
12
12
|
import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
13
13
|
import { encodeHex } from "byte-encodings/hex";
|
|
14
|
+
import { FetchError, UrlError, getDocumentLoader, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
14
15
|
import { uniq } from "es-toolkit";
|
|
15
|
-
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
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
16
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
17
|
+
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";
|
|
18
18
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
19
19
|
import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
20
20
|
import { parse, stringifyAsync } from "devalue";
|
|
@@ -1296,6 +1296,474 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
|
|
|
1296
1296
|
return `collectionId="${collectionId}", url="${url}", digest="${encodeHex(await digest(actorIds))}"`;
|
|
1297
1297
|
}
|
|
1298
1298
|
//#endregion
|
|
1299
|
+
//#region src/sig/compound-proof.ts
|
|
1300
|
+
const textEncoder = new TextEncoder();
|
|
1301
|
+
const PORTABLE_OBJECT_ID_PATTERN = /^ap(?:\+ef61)?:\/\//i;
|
|
1302
|
+
function isJsonMap(value) {
|
|
1303
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
1304
|
+
}
|
|
1305
|
+
function isCompoundProofJsonObject(value) {
|
|
1306
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
1307
|
+
}
|
|
1308
|
+
function hasOnlyDataProperties(value) {
|
|
1309
|
+
try {
|
|
1310
|
+
return Reflect.ownKeys(value).every((key) => {
|
|
1311
|
+
if (typeof key !== "string") return false;
|
|
1312
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1313
|
+
return descriptor != null && "value" in descriptor && descriptor.enumerable;
|
|
1314
|
+
});
|
|
1315
|
+
} catch {
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
function hasOnlyJsonArrayProperties(value) {
|
|
1320
|
+
try {
|
|
1321
|
+
const keys = Reflect.ownKeys(value);
|
|
1322
|
+
if (keys.length !== value.length + 1) return false;
|
|
1323
|
+
for (const key of keys) {
|
|
1324
|
+
if (key === "length") continue;
|
|
1325
|
+
if (typeof key !== "string") return false;
|
|
1326
|
+
const index = Number(key);
|
|
1327
|
+
if (!Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== key) return false;
|
|
1328
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
1329
|
+
if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
1330
|
+
}
|
|
1331
|
+
return true;
|
|
1332
|
+
} catch {
|
|
1333
|
+
return false;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function escapeJsonPointerSegment(segment) {
|
|
1337
|
+
return segment.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
1338
|
+
}
|
|
1339
|
+
function childPath(path, segment) {
|
|
1340
|
+
return `${path}/${escapeJsonPointerSegment(segment)}`;
|
|
1341
|
+
}
|
|
1342
|
+
function comparePaths(left, right) {
|
|
1343
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
1344
|
+
}
|
|
1345
|
+
function encodedLength(value) {
|
|
1346
|
+
return textEncoder.encode(value).byteLength;
|
|
1347
|
+
}
|
|
1348
|
+
function validateLimits(limits) {
|
|
1349
|
+
for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative safe integer.`);
|
|
1350
|
+
}
|
|
1351
|
+
function limitExceeded(limit, maximum, actual, path) {
|
|
1352
|
+
return {
|
|
1353
|
+
status: "unsupported",
|
|
1354
|
+
reason: {
|
|
1355
|
+
type: "limitExceeded",
|
|
1356
|
+
limit,
|
|
1357
|
+
maximum,
|
|
1358
|
+
actual,
|
|
1359
|
+
path
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
function addBytes(statistics, bytes, limits, path) {
|
|
1364
|
+
statistics.byteLength += bytes;
|
|
1365
|
+
if (statistics.byteLength > limits.maxBytes) return limitExceeded("bytes", limits.maxBytes, statistics.byteLength, path);
|
|
1366
|
+
}
|
|
1367
|
+
function inspectJsonTree(root, limits) {
|
|
1368
|
+
if (!isJsonMap(root)) return {
|
|
1369
|
+
status: "unsupported",
|
|
1370
|
+
reason: {
|
|
1371
|
+
type: "invalidJsonTree",
|
|
1372
|
+
path: ""
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
const statistics = {
|
|
1376
|
+
byteLength: 0,
|
|
1377
|
+
mapCount: 0,
|
|
1378
|
+
proofCount: 0,
|
|
1379
|
+
maxDepth: 0
|
|
1380
|
+
};
|
|
1381
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1382
|
+
const pending = [{
|
|
1383
|
+
value: root,
|
|
1384
|
+
path: "",
|
|
1385
|
+
depth: 0,
|
|
1386
|
+
insideProof: false,
|
|
1387
|
+
insideContext: false
|
|
1388
|
+
}];
|
|
1389
|
+
while (pending.length > 0) {
|
|
1390
|
+
const current = pending.pop();
|
|
1391
|
+
statistics.maxDepth = Math.max(statistics.maxDepth, current.depth);
|
|
1392
|
+
if (current.depth > limits.maxDepth) return limitExceeded("depth", limits.maxDepth, current.depth, current.path);
|
|
1393
|
+
const value = current.value;
|
|
1394
|
+
if (value === null) {
|
|
1395
|
+
const failure = addBytes(statistics, 4, limits, current.path);
|
|
1396
|
+
if (failure != null) return failure;
|
|
1397
|
+
} else if (typeof value === "boolean") {
|
|
1398
|
+
const failure = addBytes(statistics, value ? 4 : 5, limits, current.path);
|
|
1399
|
+
if (failure != null) return failure;
|
|
1400
|
+
} else if (typeof value === "string") {
|
|
1401
|
+
const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
|
|
1402
|
+
if (failure != null) return failure;
|
|
1403
|
+
} else if (typeof value === "number") {
|
|
1404
|
+
if (!Number.isFinite(value)) return {
|
|
1405
|
+
status: "unsupported",
|
|
1406
|
+
reason: {
|
|
1407
|
+
type: "invalidJsonTree",
|
|
1408
|
+
path: current.path
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
|
|
1412
|
+
if (failure != null) return failure;
|
|
1413
|
+
} else if (Array.isArray(value)) {
|
|
1414
|
+
if (seen.has(value) || !hasOnlyJsonArrayProperties(value)) return {
|
|
1415
|
+
status: "unsupported",
|
|
1416
|
+
reason: {
|
|
1417
|
+
type: "invalidJsonTree",
|
|
1418
|
+
path: current.path
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
seen.add(value);
|
|
1422
|
+
const failure = addBytes(statistics, 2 + Math.max(0, value.length - 1), limits, current.path);
|
|
1423
|
+
if (failure != null) return failure;
|
|
1424
|
+
for (let index = value.length - 1; index >= 0; index--) pending.push({
|
|
1425
|
+
value: value[index],
|
|
1426
|
+
path: childPath(current.path, String(index)),
|
|
1427
|
+
depth: current.depth + 1,
|
|
1428
|
+
insideProof: current.insideProof,
|
|
1429
|
+
insideContext: current.insideContext
|
|
1430
|
+
});
|
|
1431
|
+
} else if (isJsonMap(value)) {
|
|
1432
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1433
|
+
if (seen.has(value) || !hasOnlyDataProperties(value) || prototype !== Object.prototype && prototype !== null) return {
|
|
1434
|
+
status: "unsupported",
|
|
1435
|
+
reason: {
|
|
1436
|
+
type: "invalidJsonTree",
|
|
1437
|
+
path: current.path
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
seen.add(value);
|
|
1441
|
+
statistics.mapCount++;
|
|
1442
|
+
if (statistics.mapCount > limits.maxMaps) return limitExceeded("maps", limits.maxMaps, statistics.mapCount, current.path);
|
|
1443
|
+
const entries = Object.entries(value).sort(([left], [right]) => comparePaths(left, right));
|
|
1444
|
+
let objectBytes = 2 + Math.max(0, entries.length - 1);
|
|
1445
|
+
for (const [key] of entries) objectBytes += encodedLength(JSON.stringify(key)) + 1;
|
|
1446
|
+
const failure = addBytes(statistics, objectBytes, limits, current.path);
|
|
1447
|
+
if (failure != null) return failure;
|
|
1448
|
+
const hasDirectProof = !current.insideProof && !current.insideContext && Object.hasOwn(value, "proof");
|
|
1449
|
+
if (hasDirectProof) {
|
|
1450
|
+
statistics.proofCount++;
|
|
1451
|
+
if (statistics.proofCount > limits.maxProofs) return limitExceeded("proofs", limits.maxProofs, statistics.proofCount, childPath(current.path, "proof"));
|
|
1452
|
+
if (!isJsonMap(value.proof)) return {
|
|
1453
|
+
status: "unsupported",
|
|
1454
|
+
reason: {
|
|
1455
|
+
type: "unsupportedProofShape",
|
|
1456
|
+
path: childPath(current.path, "proof")
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
1461
|
+
const [key, child] = entries[index];
|
|
1462
|
+
pending.push({
|
|
1463
|
+
value: child,
|
|
1464
|
+
path: childPath(current.path, key),
|
|
1465
|
+
depth: current.depth + 1,
|
|
1466
|
+
insideProof: current.insideProof || hasDirectProof && key === "proof",
|
|
1467
|
+
insideContext: current.insideContext || key === "@context"
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
} else return {
|
|
1471
|
+
status: "unsupported",
|
|
1472
|
+
reason: {
|
|
1473
|
+
type: "invalidJsonTree",
|
|
1474
|
+
path: current.path
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
return statistics;
|
|
1479
|
+
}
|
|
1480
|
+
function freezeJsonTree(root) {
|
|
1481
|
+
const pending = [root];
|
|
1482
|
+
while (pending.length > 0) {
|
|
1483
|
+
const value = pending.pop();
|
|
1484
|
+
if (typeof value !== "object" || value == null) continue;
|
|
1485
|
+
if (Array.isArray(value)) for (const child of value) pending.push(child);
|
|
1486
|
+
else for (const child of Object.values(value)) pending.push(child);
|
|
1487
|
+
Object.freeze(value);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
function collectDocuments(snapshot) {
|
|
1491
|
+
const documents = [];
|
|
1492
|
+
const pending = [{
|
|
1493
|
+
value: snapshot,
|
|
1494
|
+
path: "",
|
|
1495
|
+
depth: 0
|
|
1496
|
+
}];
|
|
1497
|
+
while (pending.length > 0) {
|
|
1498
|
+
const current = pending.pop();
|
|
1499
|
+
if (Array.isArray(current.value)) {
|
|
1500
|
+
for (let index = current.value.length - 1; index >= 0; index--) pending.push({
|
|
1501
|
+
value: current.value[index],
|
|
1502
|
+
path: childPath(current.path, String(index)),
|
|
1503
|
+
depth: current.depth + 1
|
|
1504
|
+
});
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
if (!isCompoundProofJsonObject(current.value)) continue;
|
|
1508
|
+
const document = current.value;
|
|
1509
|
+
if (Object.hasOwn(document, "proof")) {
|
|
1510
|
+
const proof = document.proof;
|
|
1511
|
+
const id = typeof document.id === "string" ? document.id : typeof document["@id"] === "string" ? document["@id"] : void 0;
|
|
1512
|
+
documents.push(Object.freeze({
|
|
1513
|
+
path: current.path,
|
|
1514
|
+
id,
|
|
1515
|
+
depth: current.depth,
|
|
1516
|
+
securedDocument: document,
|
|
1517
|
+
proof
|
|
1518
|
+
}));
|
|
1519
|
+
}
|
|
1520
|
+
const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
|
|
1521
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
1522
|
+
const [key, value] = entries[index];
|
|
1523
|
+
if (key === "proof" || key === "@context") continue;
|
|
1524
|
+
pending.push({
|
|
1525
|
+
value,
|
|
1526
|
+
path: childPath(current.path, key),
|
|
1527
|
+
depth: current.depth + 1
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
documents.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
|
|
1532
|
+
return documents;
|
|
1533
|
+
}
|
|
1534
|
+
function collectPortableObjects(snapshot) {
|
|
1535
|
+
const objects = [];
|
|
1536
|
+
const pending = [{
|
|
1537
|
+
value: snapshot,
|
|
1538
|
+
path: "",
|
|
1539
|
+
depth: 0
|
|
1540
|
+
}];
|
|
1541
|
+
while (pending.length > 0) {
|
|
1542
|
+
const current = pending.pop();
|
|
1543
|
+
if (Array.isArray(current.value)) {
|
|
1544
|
+
for (let index = current.value.length - 1; index >= 0; index--) pending.push({
|
|
1545
|
+
value: current.value[index],
|
|
1546
|
+
path: childPath(current.path, String(index)),
|
|
1547
|
+
depth: current.depth + 1
|
|
1548
|
+
});
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
if (!isCompoundProofJsonObject(current.value)) continue;
|
|
1552
|
+
const document = current.value;
|
|
1553
|
+
const id = [document.id, document["@id"]].find((value) => typeof value === "string" && PORTABLE_OBJECT_ID_PATTERN.test(value));
|
|
1554
|
+
if (typeof id === "string") objects.push(Object.freeze({
|
|
1555
|
+
path: current.path,
|
|
1556
|
+
id,
|
|
1557
|
+
depth: current.depth,
|
|
1558
|
+
document
|
|
1559
|
+
}));
|
|
1560
|
+
const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
|
|
1561
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
1562
|
+
const [key, value] = entries[index];
|
|
1563
|
+
if (key === "proof" || key === "@context") continue;
|
|
1564
|
+
pending.push({
|
|
1565
|
+
value,
|
|
1566
|
+
path: childPath(current.path, key),
|
|
1567
|
+
depth: current.depth + 1
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
objects.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
|
|
1572
|
+
return objects;
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Determines whether a received JSON tree contains a portable object ID
|
|
1576
|
+
* outside a proof or context definition, without exceeding discovery limits.
|
|
1577
|
+
*
|
|
1578
|
+
* This is only an applicability check. It does not authenticate the input;
|
|
1579
|
+
* callers must still use {@link verifyCompoundPortableObjectProofs} to obtain
|
|
1580
|
+
* an atomic result from an immutable snapshot. `indeterminate` means the
|
|
1581
|
+
* applicability scan itself reached a limit, so a caller must not treat the
|
|
1582
|
+
* document as outside the compound profile.
|
|
1583
|
+
*
|
|
1584
|
+
* @internal
|
|
1585
|
+
*/
|
|
1586
|
+
function inspectCompoundPortableObjectApplicability(json, limits) {
|
|
1587
|
+
validateLimits(limits);
|
|
1588
|
+
let byteLength = 0;
|
|
1589
|
+
let mapCount = 0;
|
|
1590
|
+
const pending = [{
|
|
1591
|
+
iterator: [json].values(),
|
|
1592
|
+
depth: 0
|
|
1593
|
+
}];
|
|
1594
|
+
const addByteLength = (length) => {
|
|
1595
|
+
byteLength += length;
|
|
1596
|
+
return byteLength <= limits.maxBytes;
|
|
1597
|
+
};
|
|
1598
|
+
while (pending.length > 0) {
|
|
1599
|
+
const frame = pending[pending.length - 1];
|
|
1600
|
+
const next = frame.iterator.next();
|
|
1601
|
+
if (next.done) {
|
|
1602
|
+
pending.pop();
|
|
1603
|
+
if (byteLength > limits.maxBytes) return "indeterminate";
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
const value = next.value;
|
|
1607
|
+
if (frame.depth > limits.maxDepth) return "indeterminate";
|
|
1608
|
+
if (value === null) {
|
|
1609
|
+
if (!addByteLength(4)) return "indeterminate";
|
|
1610
|
+
} else if (typeof value === "boolean") {
|
|
1611
|
+
if (!addByteLength(value ? 4 : 5)) return "indeterminate";
|
|
1612
|
+
} else if (typeof value === "string" || typeof value === "number") {
|
|
1613
|
+
if (!addByteLength(encodedLength(JSON.stringify(value)))) return "indeterminate";
|
|
1614
|
+
} else if (Array.isArray(value)) {
|
|
1615
|
+
if (!addByteLength(2 + Math.max(0, value.length - 1))) return "indeterminate";
|
|
1616
|
+
pending.push({
|
|
1617
|
+
iterator: value.values(),
|
|
1618
|
+
depth: frame.depth + 1
|
|
1619
|
+
});
|
|
1620
|
+
} else if (isJsonMap(value)) {
|
|
1621
|
+
mapCount++;
|
|
1622
|
+
if (mapCount > limits.maxMaps) return "indeterminate";
|
|
1623
|
+
if ([value.id, value["@id"]].some((id) => typeof id === "string" && PORTABLE_OBJECT_ID_PATTERN.test(id))) return "present";
|
|
1624
|
+
if (!addByteLength(2)) return "indeterminate";
|
|
1625
|
+
const children = function* () {
|
|
1626
|
+
let first = true;
|
|
1627
|
+
for (const key in value) {
|
|
1628
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
1629
|
+
if (key === "proof" || key === "@context") continue;
|
|
1630
|
+
if (!first && !addByteLength(1)) return;
|
|
1631
|
+
first = false;
|
|
1632
|
+
if (!addByteLength(encodedLength(JSON.stringify(key)) + 1)) return;
|
|
1633
|
+
yield value[key];
|
|
1634
|
+
}
|
|
1635
|
+
}();
|
|
1636
|
+
pending.push({
|
|
1637
|
+
iterator: children,
|
|
1638
|
+
depth: frame.depth + 1
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
if (byteLength > limits.maxBytes) return "indeterminate";
|
|
1642
|
+
}
|
|
1643
|
+
return "absent";
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Discovers direct literal proof-bearing maps in an immutable JSON snapshot.
|
|
1647
|
+
*
|
|
1648
|
+
* Discovery is bounded and atomic. Unsupported proof shapes, non-JSON input,
|
|
1649
|
+
* or a resource-limit failure return no partial candidate list. Proof
|
|
1650
|
+
* configuration subtrees are validated and counted as JSON, but are not
|
|
1651
|
+
* themselves searched for secured documents.
|
|
1652
|
+
*/
|
|
1653
|
+
function discoverCompoundProofDocuments(json, limits) {
|
|
1654
|
+
validateLimits(limits);
|
|
1655
|
+
let inspected;
|
|
1656
|
+
try {
|
|
1657
|
+
inspected = inspectJsonTree(json, limits);
|
|
1658
|
+
} catch {
|
|
1659
|
+
return {
|
|
1660
|
+
status: "unsupported",
|
|
1661
|
+
reason: {
|
|
1662
|
+
type: "invalidJsonTree",
|
|
1663
|
+
path: ""
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
if ("status" in inspected) return inspected;
|
|
1668
|
+
let snapshot;
|
|
1669
|
+
try {
|
|
1670
|
+
snapshot = structuredClone(json);
|
|
1671
|
+
} catch {
|
|
1672
|
+
return {
|
|
1673
|
+
status: "unsupported",
|
|
1674
|
+
reason: {
|
|
1675
|
+
type: "invalidJsonTree",
|
|
1676
|
+
path: ""
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
freezeJsonTree(snapshot);
|
|
1681
|
+
return {
|
|
1682
|
+
status: "ok",
|
|
1683
|
+
snapshot,
|
|
1684
|
+
documents: Object.freeze(collectDocuments(snapshot)),
|
|
1685
|
+
statistics: Object.freeze({ ...inspected })
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
async function verifyDiscoveredProofDocuments(discovered, options) {
|
|
1689
|
+
const documents = await Promise.all(discovered.documents.map(async (document) => {
|
|
1690
|
+
const metadata = {
|
|
1691
|
+
path: document.path,
|
|
1692
|
+
...document.id == null ? {} : { id: document.id },
|
|
1693
|
+
depth: document.depth
|
|
1694
|
+
};
|
|
1695
|
+
if (document.depth > 0 && !Object.hasOwn(document.securedDocument, "@context")) return Object.freeze({
|
|
1696
|
+
...metadata,
|
|
1697
|
+
verified: false,
|
|
1698
|
+
reason: { type: "missingContext" }
|
|
1699
|
+
});
|
|
1700
|
+
const key = await verifyMapLocalProof(document.securedDocument, options);
|
|
1701
|
+
return key == null ? Object.freeze({
|
|
1702
|
+
...metadata,
|
|
1703
|
+
verified: false,
|
|
1704
|
+
reason: { type: "invalidProof" }
|
|
1705
|
+
}) : Object.freeze({
|
|
1706
|
+
...metadata,
|
|
1707
|
+
verified: true,
|
|
1708
|
+
key
|
|
1709
|
+
});
|
|
1710
|
+
}));
|
|
1711
|
+
return Object.freeze(documents);
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Applies FEP-ef61 portable-object policy to every portable map in a compound
|
|
1715
|
+
* snapshot without changing the map-local cryptographic inputs.
|
|
1716
|
+
*
|
|
1717
|
+
* @internal
|
|
1718
|
+
*/
|
|
1719
|
+
async function verifyCompoundPortableObjectProofs(json, limits, options = {}) {
|
|
1720
|
+
const discovered = discoverCompoundProofDocuments(json, limits);
|
|
1721
|
+
if (discovered.status !== "ok") return discovered;
|
|
1722
|
+
const proofs = await verifyDiscoveredProofDocuments(discovered, options);
|
|
1723
|
+
const proofByPath = new Map(proofs.map((proof) => [proof.path, proof]));
|
|
1724
|
+
const portableObjects = await Promise.all(collectPortableObjects(discovered.snapshot).map(async (object) => {
|
|
1725
|
+
const metadata = {
|
|
1726
|
+
path: object.path,
|
|
1727
|
+
id: object.id,
|
|
1728
|
+
depth: object.depth
|
|
1729
|
+
};
|
|
1730
|
+
if (object.depth > 0 && !Object.hasOwn(object.document, "@context")) return Object.freeze({
|
|
1731
|
+
...metadata,
|
|
1732
|
+
verified: false,
|
|
1733
|
+
reason: { type: "missingContext" }
|
|
1734
|
+
});
|
|
1735
|
+
const proof = proofByPath.get(object.path);
|
|
1736
|
+
const key = proof?.verified === true ? proof.key : null;
|
|
1737
|
+
try {
|
|
1738
|
+
const policy = await verifyPortableObjectProofPolicy(object.document, key, options);
|
|
1739
|
+
return policy.verified ? Object.freeze({
|
|
1740
|
+
...metadata,
|
|
1741
|
+
id: policy.objectId,
|
|
1742
|
+
verified: true,
|
|
1743
|
+
keys: policy.keys
|
|
1744
|
+
}) : Object.freeze({
|
|
1745
|
+
...metadata,
|
|
1746
|
+
verified: false,
|
|
1747
|
+
reason: policy.reason
|
|
1748
|
+
});
|
|
1749
|
+
} catch {
|
|
1750
|
+
return Object.freeze({
|
|
1751
|
+
...metadata,
|
|
1752
|
+
verified: false,
|
|
1753
|
+
reason: { type: "invalidPortableObject" }
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
}));
|
|
1757
|
+
return {
|
|
1758
|
+
status: "ok",
|
|
1759
|
+
verified: portableObjects.length > 0 && proofs.every((proof) => proof.verified) && portableObjects.every((object) => object.verified),
|
|
1760
|
+
snapshot: discovered.snapshot,
|
|
1761
|
+
proofs,
|
|
1762
|
+
portableObjects: Object.freeze(portableObjects),
|
|
1763
|
+
statistics: discovered.statistics
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
//#endregion
|
|
1299
1767
|
//#region src/federation/inbox.ts
|
|
1300
1768
|
async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
|
|
1301
1769
|
const logger = getLogger([
|
|
@@ -1455,6 +1923,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
1455
1923
|
}
|
|
1456
1924
|
//#endregion
|
|
1457
1925
|
//#region src/federation/keycache.ts
|
|
1926
|
+
const KEY_CACHE_GENERATION = "2";
|
|
1458
1927
|
var KvKeyCache = class {
|
|
1459
1928
|
kv;
|
|
1460
1929
|
prefix;
|
|
@@ -1477,13 +1946,20 @@ var KvKeyCache = class {
|
|
|
1477
1946
|
keyId.href
|
|
1478
1947
|
];
|
|
1479
1948
|
}
|
|
1949
|
+
#entryKey(keyId) {
|
|
1950
|
+
return [
|
|
1951
|
+
...this.prefix,
|
|
1952
|
+
KEY_CACHE_GENERATION,
|
|
1953
|
+
keyId.href
|
|
1954
|
+
];
|
|
1955
|
+
}
|
|
1480
1956
|
async get(keyId) {
|
|
1481
1957
|
const negativeExpiration = this.nullKeys.get(keyId.href);
|
|
1482
1958
|
if (negativeExpiration != null) {
|
|
1483
1959
|
if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
|
|
1484
1960
|
this.nullKeys.delete(keyId.href);
|
|
1485
1961
|
}
|
|
1486
|
-
const serialized = await this.kv.get(
|
|
1962
|
+
const serialized = await this.kv.get(this.#entryKey(keyId));
|
|
1487
1963
|
if (serialized === void 0) return void 0;
|
|
1488
1964
|
if (serialized === null) {
|
|
1489
1965
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
@@ -1495,7 +1971,7 @@ var KvKeyCache = class {
|
|
|
1495
1971
|
try {
|
|
1496
1972
|
return await Multikey.fromJsonLd(serialized, this.options);
|
|
1497
1973
|
} catch {
|
|
1498
|
-
await this.kv.delete(
|
|
1974
|
+
await this.kv.delete(this.#entryKey(keyId));
|
|
1499
1975
|
return;
|
|
1500
1976
|
}
|
|
1501
1977
|
}
|
|
@@ -1503,12 +1979,12 @@ var KvKeyCache = class {
|
|
|
1503
1979
|
async set(keyId, key) {
|
|
1504
1980
|
if (key == null) {
|
|
1505
1981
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
1506
|
-
await this.kv.set(
|
|
1982
|
+
await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
|
|
1507
1983
|
return;
|
|
1508
1984
|
}
|
|
1509
1985
|
this.nullKeys.delete(keyId.href);
|
|
1510
1986
|
const serialized = await key.toJsonLd(this.options);
|
|
1511
|
-
await this.kv.set(
|
|
1987
|
+
await this.kv.set(this.#entryKey(keyId), serialized, { ttl: this.keyTtl });
|
|
1512
1988
|
}
|
|
1513
1989
|
async getFetchError(keyId) {
|
|
1514
1990
|
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
|
|
@@ -1731,6 +2207,12 @@ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
|
|
|
1731
2207
|
//#endregion
|
|
1732
2208
|
//#region src/federation/handler.ts
|
|
1733
2209
|
const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
|
|
2210
|
+
const INBOX_COMPOUND_PROOF_LIMITS = {
|
|
2211
|
+
maxDepth: 64,
|
|
2212
|
+
maxMaps: 1e4,
|
|
2213
|
+
maxProofs: 32,
|
|
2214
|
+
maxBytes: 10 * 1024 * 1024
|
|
2215
|
+
};
|
|
1734
2216
|
function isRemoteContextLoadingFailure$1(error) {
|
|
1735
2217
|
return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
|
|
1736
2218
|
}
|
|
@@ -2511,8 +2993,16 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
2511
2993
|
}
|
|
2512
2994
|
let json;
|
|
2513
2995
|
try {
|
|
2514
|
-
json = await request.clone().
|
|
2996
|
+
json = JSON.parse(await readBoundedText(request.clone(), MAX_BODY_SIZE, request.url));
|
|
2515
2997
|
} catch (error) {
|
|
2998
|
+
if (error instanceof BodyTooLargeError) {
|
|
2999
|
+
request.body?.cancel(error).catch(() => {});
|
|
3000
|
+
span.setStatus({
|
|
3001
|
+
code: SpanStatusCode.ERROR,
|
|
3002
|
+
message: String(error)
|
|
3003
|
+
});
|
|
3004
|
+
return new Response("Inbox body too large.", { status: 413 });
|
|
3005
|
+
}
|
|
2516
3006
|
logger.error("Failed to parse JSON:\n{error}", {
|
|
2517
3007
|
recipient,
|
|
2518
3008
|
error
|
|
@@ -2844,6 +3334,31 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
2844
3334
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
2845
3335
|
});
|
|
2846
3336
|
}
|
|
3337
|
+
if (!skipSignatureVerification) {
|
|
3338
|
+
if (inspectCompoundPortableObjectApplicability(json, INBOX_COMPOUND_PROOF_LIMITS) !== "absent") {
|
|
3339
|
+
const compoundProof = await verifyCompoundPortableObjectProofs(json, INBOX_COMPOUND_PROOF_LIMITS, {
|
|
3340
|
+
documentLoader: ctx.documentLoader,
|
|
3341
|
+
keyCache,
|
|
3342
|
+
meterProvider,
|
|
3343
|
+
tracerProvider
|
|
3344
|
+
});
|
|
3345
|
+
if (compoundProof.status !== "ok" || !compoundProof.verified) {
|
|
3346
|
+
logger.error("Failed to verify compound portable Object Integrity Proofs.", {
|
|
3347
|
+
recipient,
|
|
3348
|
+
activity: json,
|
|
3349
|
+
reason: compoundProof.status === "unsupported" ? compoundProof.reason.type : "invalidProof"
|
|
3350
|
+
});
|
|
3351
|
+
span.setStatus({
|
|
3352
|
+
code: SpanStatusCode.ERROR,
|
|
3353
|
+
message: "Failed to verify compound portable Object Integrity Proofs."
|
|
3354
|
+
});
|
|
3355
|
+
return new Response("Failed to verify compound portable Object Integrity Proofs.", {
|
|
3356
|
+
status: 401,
|
|
3357
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
3358
|
+
});
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
2847
3362
|
if (pendingNonceLabel != null) {
|
|
2848
3363
|
if (!await verifySignatureNonce(request, kv, kvPrefixes.acceptSignatureNonce, pendingNonceLabel)) {
|
|
2849
3364
|
getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("invalidNonce", httpSigKey?.id == null ? void 0 : getRemoteHost(httpSigKey.id));
|
|
@@ -3644,7 +4159,7 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
3644
4159
|
if (truncated) result += "… (truncated)";
|
|
3645
4160
|
return result;
|
|
3646
4161
|
}
|
|
3647
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
4162
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
3648
4163
|
const logger = getLogger([
|
|
3649
4164
|
"fedify",
|
|
3650
4165
|
"federation",
|
|
@@ -3653,12 +4168,25 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
3653
4168
|
const federationMetrics = getFederationMetrics(meterProvider);
|
|
3654
4169
|
const started = performance.now();
|
|
3655
4170
|
let deliverySuccess = false;
|
|
4171
|
+
async function validateUrl(url) {
|
|
4172
|
+
if (!allowPrivateAddress) try {
|
|
4173
|
+
await validatePublicUrl(url);
|
|
4174
|
+
} catch (error) {
|
|
4175
|
+
if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
|
|
4176
|
+
url,
|
|
4177
|
+
error
|
|
4178
|
+
});
|
|
4179
|
+
throw error;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
await validateUrl(inbox.href);
|
|
3656
4183
|
headers = new Headers(headers);
|
|
3657
4184
|
headers.set("Content-Type", "application/activity+json");
|
|
3658
4185
|
const request = new Request(inbox, {
|
|
3659
4186
|
method: "POST",
|
|
3660
4187
|
headers,
|
|
3661
|
-
body: JSON.stringify(activity)
|
|
4188
|
+
body: JSON.stringify(activity),
|
|
4189
|
+
redirect: "manual"
|
|
3662
4190
|
});
|
|
3663
4191
|
let rsaKey = null;
|
|
3664
4192
|
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
@@ -3674,19 +4202,20 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
3674
4202
|
});
|
|
3675
4203
|
let response;
|
|
3676
4204
|
try {
|
|
3677
|
-
response = rsaKey == null ? await
|
|
4205
|
+
response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await doubleKnock(request, rsaKey, {
|
|
3678
4206
|
tracerProvider,
|
|
3679
|
-
specDeterminer
|
|
4207
|
+
specDeterminer,
|
|
4208
|
+
validateRedirect: validateUrl
|
|
3680
4209
|
});
|
|
3681
4210
|
} catch (error) {
|
|
3682
|
-
const
|
|
4211
|
+
const failure = error instanceof FetchError || error instanceof UrlError ? error : createFetchError(inbox.href, error);
|
|
3683
4212
|
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
3684
4213
|
activityId,
|
|
3685
4214
|
inbox: inbox.href,
|
|
3686
|
-
error:
|
|
4215
|
+
error: failure
|
|
3687
4216
|
});
|
|
3688
4217
|
federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
3689
|
-
throw
|
|
4218
|
+
throw failure;
|
|
3690
4219
|
}
|
|
3691
4220
|
try {
|
|
3692
4221
|
if (!response.ok) {
|
|
@@ -3723,6 +4252,49 @@ function createFetchError(url, cause) {
|
|
|
3723
4252
|
error.cause = cause;
|
|
3724
4253
|
return error;
|
|
3725
4254
|
}
|
|
4255
|
+
async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
4256
|
+
let body = await request.clone().text();
|
|
4257
|
+
for (let redirects = 0;; redirects++) {
|
|
4258
|
+
const response = await fetch(request, { redirect: "manual" });
|
|
4259
|
+
const location = response.headers.get("Location");
|
|
4260
|
+
if (![
|
|
4261
|
+
301,
|
|
4262
|
+
302,
|
|
4263
|
+
303,
|
|
4264
|
+
307,
|
|
4265
|
+
308
|
|
4266
|
+
].includes(response.status) || location == null) return response;
|
|
4267
|
+
await response.body?.cancel();
|
|
4268
|
+
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
4269
|
+
const url = new URL(location, request.url);
|
|
4270
|
+
await validateUrl(url.href);
|
|
4271
|
+
const headers = new Headers(request.headers);
|
|
4272
|
+
if (url.origin !== new URL(request.url).origin) {
|
|
4273
|
+
headers.delete("Authorization");
|
|
4274
|
+
headers.delete("Proxy-Authorization");
|
|
4275
|
+
headers.delete("Cookie");
|
|
4276
|
+
headers.delete("Host");
|
|
4277
|
+
}
|
|
4278
|
+
let method = request.method;
|
|
4279
|
+
if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
|
|
4280
|
+
method = "GET";
|
|
4281
|
+
body = void 0;
|
|
4282
|
+
for (const name of [
|
|
4283
|
+
"Content-Encoding",
|
|
4284
|
+
"Content-Language",
|
|
4285
|
+
"Content-Length",
|
|
4286
|
+
"Content-Location",
|
|
4287
|
+
"Content-Type"
|
|
4288
|
+
]) headers.delete(name);
|
|
4289
|
+
}
|
|
4290
|
+
request = new Request(url, {
|
|
4291
|
+
method,
|
|
4292
|
+
headers,
|
|
4293
|
+
body,
|
|
4294
|
+
redirect: "manual"
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
3726
4298
|
/**
|
|
3727
4299
|
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
3728
4300
|
* It contains structured information about the failure, including the HTTP
|
|
@@ -5213,6 +5785,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5213
5785
|
if (decision.stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, decision.stateChange);
|
|
5214
5786
|
}
|
|
5215
5787
|
await sendActivity({
|
|
5788
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
5216
5789
|
keys,
|
|
5217
5790
|
activity: message.activity,
|
|
5218
5791
|
activityId: message.activityId,
|
|
@@ -5834,6 +6407,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5834
6407
|
});
|
|
5835
6408
|
const promises = [];
|
|
5836
6409
|
for (const inbox in inboxes) promises.push(sendActivity({
|
|
6410
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
5837
6411
|
keys,
|
|
5838
6412
|
activity: jsonLd,
|
|
5839
6413
|
activityId: activity.id?.href,
|
|
@@ -7168,6 +7742,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
7168
7742
|
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
7169
7743
|
const promises = [];
|
|
7170
7744
|
for (const inbox in inboxes) promises.push(sendActivity({
|
|
7745
|
+
allowPrivateAddress: ctx.federation.allowPrivateAddress,
|
|
7171
7746
|
keys,
|
|
7172
7747
|
activity: ctx.activity,
|
|
7173
7748
|
activityId: ctx.activityId,
|