@fedify/fedify 2.5.0-dev.2446 → 2.5.0-dev.2492
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/README.md +3 -0
- package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
- package/dist/{builder-Da8b9O6v.mjs → builder-DphK9v7f.mjs} +5 -5
- package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.cts} +0 -1
- package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.ts} +0 -1
- package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
- package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
- package/dist/{context-BdB54V-A.d.ts → context-DQJE9YfH.d.ts} +332 -6
- package/dist/{context-CQ9H8vNW.d.cts → context-DSuL3YWx.d.cts} +332 -6
- package/dist/{deno-eNpfj4oR.mjs → deno-iOAA7iRN.mjs} +1 -1
- package/dist/docloader-DCZkiTeF.mjs +136 -0
- package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +6 -3
- package/dist/federation/handler-compound-proof.test.mjs +5 -5
- package/dist/federation/handler.test.mjs +22 -7
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-actorless.test.d.mts +2 -0
- package/dist/federation/inbox-actorless.test.mjs +487 -0
- package/dist/federation/inbox-authentication.test.d.mts +2 -0
- package/dist/federation/inbox-authentication.test.mjs +116 -0
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox-view.test.d.mts +2 -0
- package/dist/federation/inbox-view.test.mjs +369 -0
- package/dist/federation/inbox.test.mjs +22 -12
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +470 -35
- package/dist/federation/mod.cjs +2 -1
- package/dist/federation/mod.d.cts +4 -4
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +8 -2
- package/dist/federation/mq.test.mjs +36 -1
- package/dist/federation/outgoing-proof.test.mjs +9 -6
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-delivery.test.mjs +2 -2
- package/dist/federation/portable-gateway-keys.test.mjs +13 -7
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +502 -93
- package/dist/federation/tasks/enqueue.test.mjs +3 -3
- package/dist/federation/tasks/tasks.test.mjs +4 -4
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{federation-BVH7drJA.cjs → federation-D0AZw_aw.cjs} +1415 -236
- package/dist/{http-BUJ5SFh6.mjs → http-BoJCMnjY.mjs} +204 -106
- package/dist/{http-DlNxxW_5.js → http-ChXUU_3c.js} +319 -123
- package/dist/{http-BfW3wyQ4.cjs → http-kv1LZw-r.cjs} +339 -125
- package/dist/inbox-BjihTKLo.mjs +312 -0
- package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
- package/dist/inbox-view-D-skypg-.mjs +385 -0
- package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
- package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
- package/dist/{kv-cache-D_m3A241.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
- package/dist/{kv-cache-7015lbrx.js → kv-cache-DQcte7ES.js} +73 -28
- package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
- package/dist/ld-CWVpo_NM.d.cts +250 -0
- package/dist/{ld-CD2CsPxq.mjs → ld-CdvcOEXQ.mjs} +52 -9
- package/dist/ld-Inagww4R.d.ts +250 -0
- package/dist/{metrics-D3LHk6Z4.mjs → metrics-CpxvUrBz.mjs} +1 -1
- package/dist/{middleware-N9eM16XE.js → middleware-BHb5toqA.js} +1413 -247
- package/dist/{middleware-CCDXvXOG.mjs → middleware-BKs2DLUF.mjs} +1 -1
- package/dist/{middleware-CzoWvrdZ.mjs → middleware-CoBLrTKX.mjs} +533 -132
- package/dist/{mod-B0dGgQsg.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
- package/dist/mod-B4kBw1-8.d.ts +154 -0
- package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
- package/dist/mod-C1nT74IG.d.cts +154 -0
- package/dist/{mod-D0bxB8jS.d.ts → mod-CIeBgsOC.d.ts} +1 -1
- package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
- package/dist/mod.cjs +6 -5
- package/dist/mod.d.cts +9 -9
- package/dist/mod.d.ts +9 -9
- package/dist/mod.js +6 -6
- package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
- package/dist/nodeinfo/client.test.mjs +337 -19
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/otel/exporter.test.mjs +257 -1
- package/dist/otel/mod.cjs +36 -30
- package/dist/otel/mod.d.cts +13 -0
- package/dist/otel/mod.d.ts +13 -0
- package/dist/otel/mod.js +36 -30
- package/dist/{owner-C5-FVX6t.mjs → owner-DTnVpUKA.mjs} +3 -3
- package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CJeYTL61.mjs} +2 -2
- package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-DciwBIia.cjs} +1 -1
- package/dist/{portable-collection-CC2zNAje.js → portable-collection-TmtpzD4E.js} +1 -1
- package/dist/{portable-key-DNc_flH-.mjs → portable-key-DcYok4ki.mjs} +1 -1
- package/dist/{proof-Cryb1c70.mjs → proof-GH7Poos_.mjs} +98 -10
- package/dist/{send-AF25pWaJ.mjs → send-BgGJh6pv.mjs} +174 -76
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +553 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +1 -1
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +5 -5
- package/dist/sig/proof.test.mjs +45 -5
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.mjs +3 -3
- package/dist/{temporal-BsAeuUJj.mjs → temporal-gJINzcL4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +288 -2
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
- package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
- package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
- package/dist/utils/docloader.test.mjs +344 -36
- 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 +7 -7
- package/dist/docloader-B-0nqOB1.mjs +0 -91
- package/dist/inbox-CHvMaIFX.mjs +0 -188
- package/dist/mod-BRpER-B0.d.ts +0 -320
- package/dist/mod-xAfaLS4T.d.cts +0 -320
- package/dist/owner-BF0L9cMt.d.cts +0 -78
- package/dist/owner-BSzr5-I8.d.ts +0 -78
|
@@ -3,15 +3,15 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { i as getPortableDid, l as isPortableId } from "./portable-key-id-Ca30-hOa.mjs";
|
|
5
5
|
import { a as verificationObservation, i as triedKey, n as observeCheck, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
|
|
6
|
-
import { n as version, t as name } from "./deno-
|
|
7
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
8
|
-
import {
|
|
6
|
+
import { n as version, t as name } from "./deno-iOAA7iRN.mjs";
|
|
7
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CpxvUrBz.mjs";
|
|
8
|
+
import { C as fetchKey, D as validateCryptoKey, b as bypassKeyCacheReads, c as getNormalizationContextLoader } from "./ld-CdvcOEXQ.mjs";
|
|
9
9
|
import { n as preloadedOnlyDocumentLoader } from "./public-audience--klBIOk1.mjs";
|
|
10
10
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
|
|
11
11
|
import { getLogger } from "@logtape/logtape";
|
|
12
12
|
import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
|
|
13
13
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
14
|
-
import { encodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, parseGatewayUrl, parseIri } from "@fedify/vocab-runtime";
|
|
14
|
+
import { encodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isTransientFetchError, parseGatewayUrl, parseIri } from "@fedify/vocab-runtime";
|
|
15
15
|
import { encodeHex } from "byte-encodings/hex";
|
|
16
16
|
import { getPortableActorGateways } from "@fedify/vocab-runtime/internal/jsonld-cache";
|
|
17
17
|
import { isPlainJsonTree, retainSignedRepresentation } from "@fedify/vocab-runtime/internal/signed-representation";
|
|
@@ -671,6 +671,12 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
|
|
|
671
671
|
keyId: proof.verificationMethodId.href,
|
|
672
672
|
error
|
|
673
673
|
});
|
|
674
|
+
const check = options[verificationObservation]?.check;
|
|
675
|
+
if (check != null && check.reason == null) check.reason = {
|
|
676
|
+
type: "keyFetchError",
|
|
677
|
+
keyId: new URL(proof.verificationMethodId.href),
|
|
678
|
+
result: { error: error instanceof Error ? error : new Error(String(error)) }
|
|
679
|
+
};
|
|
674
680
|
return null;
|
|
675
681
|
}
|
|
676
682
|
const publicKey = fetchedKey.key;
|
|
@@ -1199,6 +1205,39 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
|
|
|
1199
1205
|
* @since 0.10.0
|
|
1200
1206
|
*/
|
|
1201
1207
|
async function verifyObject(cls, jsonLd, options = {}) {
|
|
1208
|
+
const result = await verifyObjectDetailed(cls, jsonLd, options);
|
|
1209
|
+
return result.status === "verified" ? result.object : null;
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Same as {@link verifyObject}, but also tells who signed the verified proofs,
|
|
1213
|
+
* and whether a failure was caused by a key that could not be obtained for
|
|
1214
|
+
* a retryable reason.
|
|
1215
|
+
* @param cls The class of the object to verify.
|
|
1216
|
+
* @param jsonLd The JSON-LD object to verify.
|
|
1217
|
+
* @param options Additional options.
|
|
1218
|
+
* @returns The result.
|
|
1219
|
+
* @throws {TypeError} If the object is invalid or unsupported.
|
|
1220
|
+
* @internal
|
|
1221
|
+
*/
|
|
1222
|
+
async function verifyObjectDetailed(cls, jsonLd, options = {}) {
|
|
1223
|
+
const classify = (result, checks) => {
|
|
1224
|
+
if (result != null) return {
|
|
1225
|
+
status: "verified",
|
|
1226
|
+
object: result.object,
|
|
1227
|
+
signers: result.signers
|
|
1228
|
+
};
|
|
1229
|
+
for (const check of checks) {
|
|
1230
|
+
if (check.status === "error" && isTransientFetchError(check.error)) return {
|
|
1231
|
+
status: "unavailable",
|
|
1232
|
+
error: check.error
|
|
1233
|
+
};
|
|
1234
|
+
if (check.status === "rejected" && check.reason.type === "keyFetchError" && isRetryableKeyFetchError(check.reason.result)) return {
|
|
1235
|
+
status: "unavailable",
|
|
1236
|
+
error: "error" in check.reason.result ? check.reason.result.error : /* @__PURE__ */ new Error(`Fetching key ${check.reason.keyId.href} failed with ${check.reason.result.status}.`)
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
return { status: "invalid" };
|
|
1240
|
+
};
|
|
1202
1241
|
if (options[verificationObservation]?.attempt == null) {
|
|
1203
1242
|
const observedOptions = {
|
|
1204
1243
|
...options,
|
|
@@ -1210,16 +1249,16 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
1210
1249
|
return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify_object", async (span) => {
|
|
1211
1250
|
try {
|
|
1212
1251
|
return await observeAttempt(observedOptions, "objectIntegrity", async (observation) => {
|
|
1213
|
-
const
|
|
1252
|
+
const result = await verifyObjectCore(cls, jsonLd, {
|
|
1214
1253
|
...options,
|
|
1215
1254
|
[verificationObservation]: observation
|
|
1216
1255
|
});
|
|
1217
|
-
if (
|
|
1256
|
+
if (result == null) {
|
|
1218
1257
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
1219
1258
|
span.setAttribute("activitypub.verification.failure_reason", observation.attempt?.reason?.type ?? "signatureVerificationFailed");
|
|
1220
1259
|
}
|
|
1221
|
-
return
|
|
1222
|
-
}, (value) => value
|
|
1260
|
+
return classify(result, observation.attempt?.checks ?? []);
|
|
1261
|
+
}, (value) => value.status === "verified");
|
|
1223
1262
|
} catch (error) {
|
|
1224
1263
|
span.setStatus({
|
|
1225
1264
|
code: SpanStatusCode.ERROR,
|
|
@@ -1231,6 +1270,15 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
1231
1270
|
}
|
|
1232
1271
|
});
|
|
1233
1272
|
}
|
|
1273
|
+
const checks = options[verificationObservation].attempt.checks;
|
|
1274
|
+
const before = checks.length;
|
|
1275
|
+
return classify(await verifyObjectCore(cls, jsonLd, options), checks.slice(before));
|
|
1276
|
+
}
|
|
1277
|
+
function isRetryableKeyFetchError(result) {
|
|
1278
|
+
if ("error" in result) return isTransientFetchError(result.error);
|
|
1279
|
+
return result.status >= 500 || result.status === 408 || result.status === 429;
|
|
1280
|
+
}
|
|
1281
|
+
async function verifyObjectCore(cls, jsonLd, options) {
|
|
1234
1282
|
const observation = options[verificationObservation];
|
|
1235
1283
|
const logger = getLogger([
|
|
1236
1284
|
"fedify",
|
|
@@ -1243,6 +1291,7 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
1243
1291
|
const defaultDocumentLoader = getDocumentLoader();
|
|
1244
1292
|
const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
|
|
1245
1293
|
const attributions = new Set(object.attributionIds.map((uri) => uri.href));
|
|
1294
|
+
const signers = [];
|
|
1246
1295
|
if (object instanceof Activity) for (const uri of object.actorIds) attributions.add(uri.href);
|
|
1247
1296
|
const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
|
|
1248
1297
|
const rawProofCandidates = isJsonLdNode(jsonLd) ? await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader) : [];
|
|
@@ -1301,6 +1350,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
1301
1350
|
continue;
|
|
1302
1351
|
}
|
|
1303
1352
|
deleteAuthenticatedAttribution(attributions, key.controllerId, proof.verificationMethodId);
|
|
1353
|
+
const signer = getAuthenticatedController(key.controllerId, proof.verificationMethodId);
|
|
1354
|
+
if (signer != null && !signers.some((s) => s.kind === signer.kind && s.id.href === signer.id.href && s.keyId.href === proof.verificationMethodId.href)) signers.push({
|
|
1355
|
+
...signer,
|
|
1356
|
+
keyId: proof.verificationMethodId
|
|
1357
|
+
});
|
|
1304
1358
|
}
|
|
1305
1359
|
if (acceptedPortableProofs < loadedPortableProofs) {
|
|
1306
1360
|
logger.debug("Some portable proof references could not be dereferenced.");
|
|
@@ -1314,7 +1368,41 @@ async function verifyObject(cls, jsonLd, options = {}) {
|
|
|
1314
1368
|
logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
|
|
1315
1369
|
return null;
|
|
1316
1370
|
}
|
|
1317
|
-
return
|
|
1371
|
+
return {
|
|
1372
|
+
object,
|
|
1373
|
+
signers
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Tells whom a key that verified an Object Integrity Proof authenticates.
|
|
1378
|
+
* @param key The key, i.e., the proof's verification method.
|
|
1379
|
+
* @returns The signer, or `null` if the key authenticates nobody.
|
|
1380
|
+
* @internal
|
|
1381
|
+
*/
|
|
1382
|
+
function getProofSigner(key) {
|
|
1383
|
+
if (key.id == null || key.controllerId == null) return null;
|
|
1384
|
+
const signer = getAuthenticatedController(key.controllerId, key.id);
|
|
1385
|
+
return signer == null ? null : {
|
|
1386
|
+
...signer,
|
|
1387
|
+
keyId: key.id
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Tells whom a verified proof authenticates: the key's controller if it is an
|
|
1392
|
+
* ordinary actor, or the DID of a portable controller if the verification
|
|
1393
|
+
* method belongs to the same DID.
|
|
1394
|
+
*/
|
|
1395
|
+
function getAuthenticatedController(controllerId, verificationMethodId) {
|
|
1396
|
+
if (!hasCryptographicOrigin(controllerId.href) && !isPortableId(controllerId)) return {
|
|
1397
|
+
kind: "actor",
|
|
1398
|
+
id: controllerId
|
|
1399
|
+
};
|
|
1400
|
+
const did = getPortableDid(controllerId);
|
|
1401
|
+
if (did == null || !hasCryptographicOrigin(verificationMethodId.href) || getPortableDid(verificationMethodId) !== did || !URL.canParse(did)) return null;
|
|
1402
|
+
return {
|
|
1403
|
+
kind: "portableAuthority",
|
|
1404
|
+
id: new URL(did)
|
|
1405
|
+
};
|
|
1318
1406
|
}
|
|
1319
1407
|
function deleteAuthenticatedAttribution(attributions, controllerId, verificationMethodId) {
|
|
1320
1408
|
if (!hasCryptographicOrigin(controllerId.href) && !isPortableId(controllerId)) {
|
|
@@ -1329,4 +1417,4 @@ function hasCryptographicOrigin(iri) {
|
|
|
1329
1417
|
return /^did:/i.test(iri) || /^ap(?:\+ef61)?:\/\//i.test(iri);
|
|
1330
1418
|
}
|
|
1331
1419
|
//#endregion
|
|
1332
|
-
export {
|
|
1420
|
+
export { hasProofLike as a, verifyMapLocalProof as c, verifyPortableObjectProof as d, verifyPortableObjectProofPolicy as f, verifyProof as h, getProofSigner as i, verifyObject as l, verifyPreparedPortableObjectProofPolicy as m, classifyFep2277CoreType as n, preparePortableObjectProofPolicy as o, verifyPortableObjectProofWithRoot as p, createProof as r, signObject as s, InvalidPortableObjectIdError as t, verifyObjectDetailed as u };
|
|
@@ -2,13 +2,14 @@ import "temporal-polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { d as isPortableUri } from "./portable-key-id-Ca30-hOa.mjs";
|
|
5
|
-
import { n as version, t as name } from "./deno-
|
|
6
|
-
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-
|
|
7
|
-
import {
|
|
5
|
+
import { n as version, t as name } from "./deno-iOAA7iRN.mjs";
|
|
6
|
+
import { n as getDurationMs, r as getFederationMetrics } from "./metrics-CpxvUrBz.mjs";
|
|
7
|
+
import { r as doubleKnockScoped } from "./http-BoJCMnjY.mjs";
|
|
8
8
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
9
9
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
10
|
-
import { FetchError, UrlError, canonicalizePortableUri, getFe34Origin, isGatewayUrl, toCompatibleEf61Id
|
|
10
|
+
import { FetchError, UrlError, canonicalizePortableUri, getFe34Origin, isGatewayUrl, toCompatibleEf61Id } from "@fedify/vocab-runtime";
|
|
11
11
|
import { getPortableGatewayCandidates } from "@fedify/vocab-runtime/internal/portable-dereference";
|
|
12
|
+
import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
|
|
12
13
|
/**
|
|
13
14
|
* Resolves a portable inbox, i.e., an `ap:` or `ap+ef61:` URI, to
|
|
14
15
|
* the compatible identifiers of the inbox on the recipient's gateways.
|
|
@@ -188,9 +189,10 @@ function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
|
|
|
188
189
|
*
|
|
189
190
|
* @param parameters The parameters for sending the activity.
|
|
190
191
|
* See also {@link SendActivityParameters}.
|
|
192
|
+
* @param deps The dependencies, for tests.
|
|
191
193
|
* @throws {Error} If the activity fails to send.
|
|
192
194
|
*/
|
|
193
|
-
function sendActivity(options) {
|
|
195
|
+
function sendActivity(options, deps = {}) {
|
|
194
196
|
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
|
195
197
|
return tracerProvider.getTracer(name, version).startActiveSpan("activitypub.send_activity", {
|
|
196
198
|
kind: SpanKind.CLIENT,
|
|
@@ -206,7 +208,7 @@ function sendActivity(options) {
|
|
|
206
208
|
}, () => sendActivityInternal({
|
|
207
209
|
...options,
|
|
208
210
|
tracerProvider
|
|
209
|
-
}, span));
|
|
211
|
+
}, span, deps));
|
|
210
212
|
} catch (e) {
|
|
211
213
|
span.setStatus({
|
|
212
214
|
code: SpanStatusCode.ERROR,
|
|
@@ -291,13 +293,14 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
291
293
|
totalBytes += value.length;
|
|
292
294
|
}
|
|
293
295
|
} finally {
|
|
296
|
+
if (truncated) reader.cancel().catch(() => {});
|
|
294
297
|
reader.releaseLock();
|
|
295
298
|
}
|
|
296
299
|
let result = chunks.join("");
|
|
297
300
|
if (truncated) result += "… (truncated)";
|
|
298
301
|
return result;
|
|
299
302
|
}
|
|
300
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
303
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span, deps) {
|
|
301
304
|
const logger = getLogger([
|
|
302
305
|
"fedify",
|
|
303
306
|
"federation",
|
|
@@ -306,9 +309,11 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
306
309
|
const federationMetrics = getFederationMetrics(meterProvider);
|
|
307
310
|
const started = performance.now();
|
|
308
311
|
let deliverySuccess = false;
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
+
const openUnvalidatedHop = deps.openHop ?? (allowPrivateAddress ? openDirectFetch : (url) => openPublicFetch(url));
|
|
313
|
+
async function openHop(url) {
|
|
314
|
+
let session;
|
|
315
|
+
try {
|
|
316
|
+
session = await openUnvalidatedHop(url);
|
|
312
317
|
} catch (error) {
|
|
313
318
|
if (error instanceof UrlError && error.reason === "dns") {
|
|
314
319
|
logger.error("DNS resolution failed for URL: {url}", {
|
|
@@ -325,82 +330,90 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
325
330
|
});
|
|
326
331
|
throw error;
|
|
327
332
|
}
|
|
333
|
+
return session;
|
|
328
334
|
}
|
|
335
|
+
let initialSession;
|
|
329
336
|
try {
|
|
330
|
-
await
|
|
337
|
+
initialSession = await openHop(inbox.href);
|
|
331
338
|
} catch (error) {
|
|
332
339
|
if (error instanceof FetchError) federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
333
340
|
throw error;
|
|
334
341
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
let rsaKey = null;
|
|
344
|
-
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
345
|
-
rsaKey = key;
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
if (rsaKey == null && keys.length < 1) logger.debug("Sending the activity {activityId} to {inbox} without a signature.", {
|
|
349
|
-
activityId,
|
|
350
|
-
inbox: inbox.href
|
|
351
|
-
});
|
|
352
|
-
else if (rsaKey == null) logger.warn("No supported key found to sign the request to {inbox}. The request will be sent without a signature. In order to sign the request, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
|
|
353
|
-
inbox: inbox.href,
|
|
354
|
-
keys: keys.map((pair) => ({
|
|
355
|
-
keyId: pair.keyId.href,
|
|
356
|
-
privateKey: pair.privateKey
|
|
357
|
-
}))
|
|
358
|
-
});
|
|
359
|
-
let response;
|
|
342
|
+
let initialPending = true;
|
|
343
|
+
const openDeliveryHop = (url) => {
|
|
344
|
+
if (initialPending) {
|
|
345
|
+
initialPending = false;
|
|
346
|
+
return Promise.resolve(initialSession);
|
|
347
|
+
}
|
|
348
|
+
return openHop(url);
|
|
349
|
+
};
|
|
360
350
|
try {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
351
|
+
headers = new Headers(headers);
|
|
352
|
+
headers.set("Content-Type", "application/activity+json");
|
|
353
|
+
const request = new Request(inbox, {
|
|
354
|
+
method: "POST",
|
|
355
|
+
headers,
|
|
356
|
+
body: JSON.stringify(activity),
|
|
357
|
+
redirect: "manual"
|
|
365
358
|
});
|
|
366
|
-
|
|
367
|
-
const
|
|
368
|
-
|
|
359
|
+
let rsaKey = null;
|
|
360
|
+
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
361
|
+
rsaKey = key;
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
if (rsaKey == null && keys.length < 1) logger.debug("Sending the activity {activityId} to {inbox} without a signature.", {
|
|
369
365
|
activityId,
|
|
366
|
+
inbox: inbox.href
|
|
367
|
+
});
|
|
368
|
+
else if (rsaKey == null) logger.warn("No supported key found to sign the request to {inbox}. The request will be sent without a signature. In order to sign the request, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
|
|
370
369
|
inbox: inbox.href,
|
|
371
|
-
|
|
370
|
+
keys: keys.map((pair) => ({
|
|
371
|
+
keyId: pair.keyId.href,
|
|
372
|
+
privateKey: pair.privateKey
|
|
373
|
+
}))
|
|
372
374
|
});
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
error = "";
|
|
383
|
-
}
|
|
384
|
-
logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
|
|
375
|
+
let outcome;
|
|
376
|
+
try {
|
|
377
|
+
outcome = rsaKey == null ? await fetchWithValidatedRedirects(request, openDeliveryHop, (response) => consumeSendActivityResponse(response, inbox)) : await doubleKnockScoped(request, rsaKey, {
|
|
378
|
+
tracerProvider,
|
|
379
|
+
specDeterminer
|
|
380
|
+
}, openDeliveryHop, (response) => consumeSendActivityResponse(response, inbox));
|
|
381
|
+
} catch (error) {
|
|
382
|
+
const failure = error instanceof FetchError || error instanceof UrlError ? error : createFetchError(inbox.href, error);
|
|
383
|
+
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
385
384
|
activityId,
|
|
386
385
|
inbox: inbox.href,
|
|
387
|
-
|
|
388
|
-
statusText: response.statusText,
|
|
389
|
-
error
|
|
386
|
+
error: failure
|
|
390
387
|
});
|
|
391
|
-
|
|
388
|
+
federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
389
|
+
throw failure;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
if (!outcome.ok) {
|
|
393
|
+
const { status, statusText, error, responseHeaders } = outcome;
|
|
394
|
+
logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
|
|
395
|
+
activityId,
|
|
396
|
+
inbox: inbox.href,
|
|
397
|
+
status,
|
|
398
|
+
statusText,
|
|
399
|
+
error
|
|
400
|
+
});
|
|
401
|
+
throw new SendActivityError(inbox, status, `Failed to send activity ${activityId} to ${inbox.href} (${status} ${statusText}):\n${error}`, error, responseHeaders);
|
|
402
|
+
}
|
|
403
|
+
deliverySuccess = true;
|
|
404
|
+
const eventAttributes = {
|
|
405
|
+
"activitypub.inbox.url": inbox.href,
|
|
406
|
+
"activitypub.activity.id": activityId ?? ""
|
|
407
|
+
};
|
|
408
|
+
if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
|
|
409
|
+
const actorId = getActivityActorId(activity);
|
|
410
|
+
if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
|
|
411
|
+
span.addEvent("activitypub.activity.sent", eventAttributes);
|
|
412
|
+
} finally {
|
|
413
|
+
federationMetrics.recordDelivery(inbox, getDurationMs(started), deliverySuccess, activityType);
|
|
392
414
|
}
|
|
393
|
-
deliverySuccess = true;
|
|
394
|
-
const eventAttributes = {
|
|
395
|
-
"activitypub.inbox.url": inbox.href,
|
|
396
|
-
"activitypub.activity.id": activityId ?? ""
|
|
397
|
-
};
|
|
398
|
-
if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
|
|
399
|
-
const actorId = getActivityActorId(activity);
|
|
400
|
-
if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
|
|
401
|
-
span.addEvent("activitypub.activity.sent", eventAttributes);
|
|
402
415
|
} finally {
|
|
403
|
-
|
|
416
|
+
if (initialPending) await initialSession.close();
|
|
404
417
|
}
|
|
405
418
|
}
|
|
406
419
|
function createFetchError(url, cause) {
|
|
@@ -408,10 +421,75 @@ function createFetchError(url, cause) {
|
|
|
408
421
|
error.cause = cause;
|
|
409
422
|
return error;
|
|
410
423
|
}
|
|
411
|
-
|
|
424
|
+
/**
|
|
425
|
+
* Starts cancelling the body of a response without waiting for it to settle.
|
|
426
|
+
* The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
|
|
427
|
+
* settles only after the other branch is cancelled too, per the Streams
|
|
428
|
+
* standard, so awaiting it could block forever. Failures are only logged.
|
|
429
|
+
*/
|
|
430
|
+
function cancelResponseBody(response, url) {
|
|
431
|
+
response.body?.cancel().catch((error) => {
|
|
432
|
+
getLogger([
|
|
433
|
+
"fedify",
|
|
434
|
+
"federation",
|
|
435
|
+
"outbox"
|
|
436
|
+
]).debug("Failed to cancel the response body from {url}: {error}", {
|
|
437
|
+
url,
|
|
438
|
+
error
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Reads what is needed from the final response of a delivery while its
|
|
444
|
+
* session is open: the body of a successful response is cancelled, and at
|
|
445
|
+
* most 1 KiB of an error response body is read.
|
|
446
|
+
*/
|
|
447
|
+
async function consumeSendActivityResponse(response, inbox) {
|
|
448
|
+
if (response.ok) {
|
|
449
|
+
cancelResponseBody(response, response.url || inbox.href);
|
|
450
|
+
return { ok: true };
|
|
451
|
+
}
|
|
452
|
+
let error;
|
|
453
|
+
try {
|
|
454
|
+
error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
|
|
455
|
+
} catch (_) {
|
|
456
|
+
error = "";
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
ok: false,
|
|
460
|
+
status: response.status,
|
|
461
|
+
statusText: response.statusText,
|
|
462
|
+
error,
|
|
463
|
+
responseHeaders: new Headers(response.headers)
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Sends a request without signing it, following redirects manually like the
|
|
468
|
+
* Fetch standard does. Every hop is opened with `openHop()`, which validates
|
|
469
|
+
* the destination, after the previous hop is closed. The final response is
|
|
470
|
+
* passed to `consume()`, and its session is closed after that.
|
|
471
|
+
* This is exported only for tests; it is not exported from any entry point.
|
|
472
|
+
* @param request The request to send.
|
|
473
|
+
* @param openHop The function to open a session for each hop.
|
|
474
|
+
* @param consume The function to consume the final response.
|
|
475
|
+
* @returns The value returned by `consume`.
|
|
476
|
+
*/
|
|
477
|
+
async function fetchWithValidatedRedirects(request, openHop, consume) {
|
|
478
|
+
const logger = getLogger([
|
|
479
|
+
"fedify",
|
|
480
|
+
"federation",
|
|
481
|
+
"outbox"
|
|
482
|
+
]);
|
|
412
483
|
let body = await request.clone().text();
|
|
413
484
|
for (let redirects = 0;; redirects++) {
|
|
414
|
-
const
|
|
485
|
+
const session = await openHop(request.url);
|
|
486
|
+
let response;
|
|
487
|
+
try {
|
|
488
|
+
response = await session.fetch(request, { redirect: "manual" });
|
|
489
|
+
} catch (error) {
|
|
490
|
+
await closeAfterError(session, logger);
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
415
493
|
const location = response.headers.get("Location");
|
|
416
494
|
if (![
|
|
417
495
|
301,
|
|
@@ -419,11 +497,21 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
|
419
497
|
303,
|
|
420
498
|
307,
|
|
421
499
|
308
|
|
422
|
-
].includes(response.status) || location == null)
|
|
423
|
-
|
|
500
|
+
].includes(response.status) || location == null) try {
|
|
501
|
+
return await consume(response);
|
|
502
|
+
} catch (error) {
|
|
503
|
+
logger.debug("Failed to consume the response from {url}; closing the session: {error}", {
|
|
504
|
+
url: request.url,
|
|
505
|
+
error
|
|
506
|
+
});
|
|
507
|
+
throw error;
|
|
508
|
+
} finally {
|
|
509
|
+
await session.close();
|
|
510
|
+
}
|
|
511
|
+
cancelResponseBody(response, request.url);
|
|
512
|
+
await session.close();
|
|
424
513
|
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
425
514
|
const url = new URL(location, request.url);
|
|
426
|
-
await validateUrl(url.href);
|
|
427
515
|
const headers = new Headers(request.headers);
|
|
428
516
|
if (url.origin !== new URL(request.url).origin) {
|
|
429
517
|
headers.delete("Authorization");
|
|
@@ -451,6 +539,16 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
|
451
539
|
});
|
|
452
540
|
}
|
|
453
541
|
}
|
|
542
|
+
async function closeAfterError(session, logger) {
|
|
543
|
+
try {
|
|
544
|
+
await session.close();
|
|
545
|
+
} catch (error) {
|
|
546
|
+
logger.debug("Failed to close the session for {url}: {error}", {
|
|
547
|
+
url: session.url.href,
|
|
548
|
+
error
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
454
552
|
/**
|
|
455
553
|
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
456
554
|
* It contains structured information about the failure, including the HTTP
|
|
@@ -496,4 +594,4 @@ var SendActivityError = class extends Error {
|
|
|
496
594
|
}
|
|
497
595
|
};
|
|
498
596
|
//#endregion
|
|
499
|
-
export {
|
|
597
|
+
export { sendActivityThroughGateways as a, sendActivity as i, extractInboxes as n, getOrderingDestination as o, fetchWithValidatedRedirects as r, SendActivityError as t };
|
|
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
5
5
|
import "../std__assert-DvMDRPes.mjs";
|
|
6
|
-
import { a as inspectCompoundPortableObjectApplicability, n as discoverCompoundProofDocuments } from "../compound-proof-
|
|
6
|
+
import { a as inspectCompoundPortableObjectApplicability, n as discoverCompoundProofDocuments } from "../compound-proof-cYtvlTgF.mjs";
|
|
7
7
|
import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
|
|
8
8
|
import { test } from "@fedify/fixture";
|
|
9
9
|
//#region src/sig/compound-proof-discovery.test.ts
|
|
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
5
5
|
import "../std__assert-DvMDRPes.mjs";
|
|
6
|
-
import { a as inspectCompoundPortableObjectApplicability, i as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "../compound-proof-
|
|
6
|
+
import { a as inspectCompoundPortableObjectApplicability, i as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "../compound-proof-cYtvlTgF.mjs";
|
|
7
7
|
import { test } from "@fedify/fixture";
|
|
8
8
|
//#region src/sig/compound-proof-producer.test.ts
|
|
9
9
|
const portableId = "ap+ef61://did:key:z6MkAlice/objects/1";
|
|
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
5
5
|
import "../std__assert-DvMDRPes.mjs";
|
|
6
6
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { c as verifyMapLocalProof, f as verifyPortableObjectProofPolicy } from "../proof-GH7Poos_.mjs";
|
|
8
8
|
import { n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
9
|
-
import { a as inspectCompoundPortableObjectApplicability, o as verifyCompoundPortableObjectProofs, s as verifyCompoundProofDocuments, t as containsCompoundPortableObject } from "../compound-proof-
|
|
9
|
+
import { a as inspectCompoundPortableObjectApplicability, o as verifyCompoundPortableObjectProofs, s as verifyCompoundProofDocuments, t as containsCompoundPortableObject } from "../compound-proof-cYtvlTgF.mjs";
|
|
10
10
|
import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
|
|
11
11
|
import { t as map_local_context_conflict_default } from "../map-local-context-conflict-DOYEEJ3u.mjs";
|
|
12
12
|
import { CryptographicKey, Multikey, Person } from "@fedify/vocab";
|
|
@@ -6,9 +6,9 @@ import "../std__assert-DvMDRPes.mjs";
|
|
|
6
6
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
7
7
|
import { n as preloadedOnlyDocumentLoader } from "../public-audience--klBIOk1.mjs";
|
|
8
8
|
import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
|
|
9
|
-
import {
|
|
9
|
+
import { h as verifyProof, s as signObject } from "../proof-GH7Poos_.mjs";
|
|
10
10
|
import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
|
|
11
|
-
import { s as verifyCompoundProofDocuments } from "../compound-proof-
|
|
11
|
+
import { s as verifyCompoundProofDocuments } from "../compound-proof-cYtvlTgF.mjs";
|
|
12
12
|
import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
|
|
13
13
|
import { t as map_local_context_conflict_default } from "../map-local-context-conflict-DOYEEJ3u.mjs";
|
|
14
14
|
import { Create, DataIntegrityProof, Note, Question } from "@fedify/vocab";
|