@fedify/fedify 2.4.0-dev.1988 → 2.4.0-dev.2010
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-Cts78G9N.mjs → builder-asm96yV1.mjs} +2 -2
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/compound-proof-7UIAueLV.mjs +495 -0
- package/dist/{deno-BgMEefEr.mjs → deno-Ckq9TwCv.mjs} +1 -1
- package/dist/{docloader-DEYs99FZ.mjs → docloader-DUKEPG-O.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 +5 -5
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +3 -3
- 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-DzPUG_AP.cjs → federation-CgbvMauV.cjs} +542 -43
- package/dist/{http-CgwqtxAy.js → http-DQA2JOEJ.js} +2 -2
- package/dist/{http-Z3anvCd-.cjs → http-DQBfmkrS.cjs} +2 -2
- package/dist/{http-CtidZS_t.mjs → http-DZaAutqg.mjs} +3 -3
- package/dist/{key-Cwbg5ggV.mjs → key-zxzx01Ec.mjs} +2 -2
- package/dist/{kv-cache-CykuL0ya.js → kv-cache-BBoePTOJ.js} +1 -1
- package/dist/{kv-cache-DHiR8yTf.mjs → kv-cache-CbrUX90H.mjs} +1 -1
- package/dist/{kv-cache-BM2-w-Wq.cjs → kv-cache-sy8OMNT8.cjs} +1 -1
- package/dist/{ld-CzhdWyTt.mjs → ld-CD_TN0e8.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-CrqsHP0l.mjs → metrics-B47Cm5gK.mjs} +1 -1
- package/dist/{middleware-CeCj5caM.js → middleware-U0OIuqwD.js} +511 -12
- package/dist/{middleware-CZpYxSKD.mjs → middleware-U_Gannqi.mjs} +1 -1
- package/dist/{middleware-DCpiAsZi.mjs → middleware-pHwDTAnq.mjs} +52 -20
- package/dist/mod.cjs +19 -19
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{proof-DHu5zI0Z.cjs → owner--mNatWct.cjs} +1252 -1140
- package/dist/{proof-CUghqhAf.js → owner-BNKC3s6G.js} +1239 -1139
- package/dist/{owner-BqVXJLW8.mjs → owner-u3IJRhVx.mjs} +2 -2
- package/dist/{proof-DL8Iid3a.mjs → proof-D3te0SP-.mjs} +176 -76
- package/dist/{send-6jS4xFsX.mjs → send-CDr0Zivk.mjs} +3 -3
- 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 +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +17 -17
- 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-DcyzeytP.mjs → temporal-DKhVF_Uk.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
|
@@ -2,10 +2,10 @@ 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 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-
|
|
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-DQA2JOEJ.js";
|
|
6
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 {
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
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-BNKC3s6G.js";
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-BBoePTOJ.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";
|
|
@@ -13,8 +13,8 @@ import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "
|
|
|
13
13
|
import { encodeHex } from "byte-encodings/hex";
|
|
14
14
|
import { FetchError, UrlError, getDocumentLoader, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
15
15
|
import { uniq } from "es-toolkit";
|
|
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([
|
|
@@ -1739,6 +2207,12 @@ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
|
|
|
1739
2207
|
//#endregion
|
|
1740
2208
|
//#region src/federation/handler.ts
|
|
1741
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
|
+
};
|
|
1742
2216
|
function isRemoteContextLoadingFailure$1(error) {
|
|
1743
2217
|
return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
|
|
1744
2218
|
}
|
|
@@ -1863,13 +2337,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
|
1863
2337
|
* @param parameters The parameters for handling the collection.
|
|
1864
2338
|
* @returns A promise that resolves to an HTTP response.
|
|
1865
2339
|
*/
|
|
1866
|
-
async function handleCollection(request, { name: name$
|
|
1867
|
-
const spanName = name$
|
|
2340
|
+
async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
2341
|
+
const spanName = name$1.trim().replace(/\s+/g, "_");
|
|
1868
2342
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
1869
2343
|
const tracer = tracerProvider.getTracer(name, version);
|
|
1870
2344
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
1871
2345
|
const metricBase = {
|
|
1872
|
-
kind: getCollectionMetricKind(name$
|
|
2346
|
+
kind: getCollectionMetricKind(name$1),
|
|
1873
2347
|
page: cursor != null,
|
|
1874
2348
|
dispatcher: "built_in"
|
|
1875
2349
|
};
|
|
@@ -1910,7 +2384,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1910
2384
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1911
2385
|
return await onNotFound(request);
|
|
1912
2386
|
}
|
|
1913
|
-
const items = filterCollectionItems(page.items, name$
|
|
2387
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
1914
2388
|
itemCount = items.length;
|
|
1915
2389
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
1916
2390
|
return items;
|
|
@@ -1950,7 +2424,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1950
2424
|
} else {
|
|
1951
2425
|
const uri = new URL(baseUri);
|
|
1952
2426
|
uri.searchParams.set("cursor", cursor);
|
|
1953
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
2427
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
|
|
1954
2428
|
kind: SpanKind.SERVER,
|
|
1955
2429
|
attributes: {
|
|
1956
2430
|
"activitypub.collection.id": uri.href,
|
|
@@ -1966,7 +2440,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
|
|
|
1966
2440
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1967
2441
|
return await onNotFound(request);
|
|
1968
2442
|
}
|
|
1969
|
-
const items = filterCollectionItems(page.items, name$
|
|
2443
|
+
const items = filterCollectionItems(page.items, name$1, filterPredicate);
|
|
1970
2444
|
itemCount = items.length;
|
|
1971
2445
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
1972
2446
|
return {
|
|
@@ -2860,6 +3334,31 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
2860
3334
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
2861
3335
|
});
|
|
2862
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
|
+
}
|
|
2863
3362
|
if (pendingNonceLabel != null) {
|
|
2864
3363
|
if (!await verifySignatureNonce(request, kv, kvPrefixes.acceptSignatureNonce, pendingNonceLabel)) {
|
|
2865
3364
|
getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("invalidNonce", httpSigKey?.id == null ? void 0 : getRemoteHost(httpSigKey.id));
|
|
@@ -3034,8 +3533,8 @@ var CustomCollectionHandler = class {
|
|
|
3034
3533
|
* @param CollectionPage The CollectionPage constructor.
|
|
3035
3534
|
* @param filterPredicate Optional filter predicate for items.
|
|
3036
3535
|
*/
|
|
3037
|
-
constructor(name$
|
|
3038
|
-
this.name = name$
|
|
3536
|
+
constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
|
|
3537
|
+
this.name = name$2;
|
|
3039
3538
|
this.values = values;
|
|
3040
3539
|
this.context = context;
|
|
3041
3540
|
this.callbacks = callbacks;
|