@fedify/fedify 2.5.0-dev.2448 → 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/{builder-DbC8bn6k.mjs → builder-DphK9v7f.mjs} +2 -2
- package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.cts} +0 -1
- package/dist/{client-D-XP_Ccb.d.ts → 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-heeXW2_A.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
- package/dist/{context-GlHrzdSP.d.ts → context-DQJE9YfH.d.ts} +29 -3
- package/dist/{context-BazwQrHn.d.cts → context-DSuL3YWx.d.cts} +29 -3
- package/dist/{deno-Dpewt_jJ.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 +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-actorless.test.mjs +5 -5
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox-view.test.mjs +1 -1
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +468 -33
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +1 -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 +11 -5
- 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 +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-D8YexkP1.cjs → federation-D0AZw_aw.cjs} +246 -108
- package/dist/{http-BZkyQIkb.mjs → http-BoJCMnjY.mjs} +204 -106
- package/dist/{http-BBr-VLil.js → http-ChXUU_3c.js} +178 -109
- package/dist/{http-CAl7Num5.cjs → http-kv1LZw-r.cjs} +179 -110
- package/dist/{inbox-wDuMKaFW.mjs → inbox-BjihTKLo.mjs} +2 -2
- package/dist/{inbox-view-NE9wXdLo.mjs → inbox-view-D-skypg-.mjs} +1 -1
- 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-CTQSfkmp.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
- package/dist/{kv-cache-U7uz5nx4.js → kv-cache-DQcte7ES.js} +73 -28
- package/dist/{kv-cache-CjgCOz3i.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
- package/dist/{ld-Cel1RRZ5.d.cts → ld-CWVpo_NM.d.cts} +1 -1
- package/dist/{ld-CQFuiah9.mjs → ld-CdvcOEXQ.mjs} +2 -2
- package/dist/{ld-CcuZ4NZa.d.ts → ld-Inagww4R.d.ts} +1 -1
- package/dist/{metrics-B-QxRSSR.mjs → metrics-CpxvUrBz.mjs} +1 -1
- package/dist/{middleware-P-AZEQMn.js → middleware-BHb5toqA.js} +247 -109
- package/dist/{middleware-BdlNa8BD.mjs → middleware-BKs2DLUF.mjs} +1 -1
- package/dist/{middleware-CuVa0UuS.mjs → middleware-CoBLrTKX.mjs} +89 -49
- package/dist/{mod-DvWOv2Ho.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
- package/dist/{mod-BHQE-7WQ.d.ts → mod-B4kBw1-8.d.ts} +1 -1
- package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
- package/dist/{mod-B2783_3v.d.cts → mod-C1nT74IG.d.cts} +1 -1
- package/dist/{mod-DjZfJj9R.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 +5 -5
- package/dist/mod.d.cts +7 -7
- package/dist/mod.d.ts +7 -7
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +337 -19
- package/dist/nodeinfo/handler.test.mjs +1 -1
- 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-ccKkQOAf.mjs → owner-DTnVpUKA.mjs} +3 -3
- package/dist/{portable-collection-CndnpXfo.mjs → portable-collection-CJeYTL61.mjs} +2 -2
- package/dist/{portable-collection-TC-kvkVI.cjs → portable-collection-DciwBIia.cjs} +1 -1
- package/dist/{portable-collection-DUlQ3s2M.js → portable-collection-TmtpzD4E.js} +1 -1
- package/dist/{portable-key-D5tFOoV-.mjs → portable-key-DcYok4ki.mjs} +1 -1
- package/dist/{proof-DPoF06nz.mjs → proof-GH7Poos_.mjs} +3 -3
- package/dist/{send-CF44O1Df.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 +4 -4
- 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-XlZ4Wb94.mjs → temporal-gJINzcL4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +26 -0
- 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 +8 -8
- package/dist/docloader-CpsDyqVN.mjs +0 -91
|
@@ -2,21 +2,22 @@ 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 { $ as getVerifiedKeyOwnerEvidence, A as compactJsonLd, At as getRawCanonicalPortableId, B as verifyCompactJsonLdDetailed, C as verifyPortableObjectProofWithRoot, Ct as observeAttempt, D as InvalidContextReferenceError, Dt as getCanonicalPortableId, E as normalizeOutgoingActivityJsonLd, Et as getAuthenticationOrigin, F as hasSignatureLike, Ft as isPortableUri, I as isClearlyMalformedContextReference, It as isSameObjectId, L as isInvalidUrlTypeError, M as detachSignature, Mt as isCompatibleKeyId, N as getNormalizationContextLoader, Nt as isPortableId, O as assertSafeJsonLd, Ot as getGatewayKeyBase, P as hasSignature, Pt as isPortableKeyId, Q as MAX_PORTABLE_KEY_TTL, R as markEffectiveContextLoader, Rt as name, S as verifyPortableObjectProofPolicy, Tt as verificationObservation, U as wrapContextLoaderForJsonLd, W as exportJwk, X as validateCryptoKey, Y as importJwk, _ as signObject, _t as registerQueueDepthGauge, at as isAbortError, b as verifyObjectDetailed, c as verifyRequestDetailed, ct as recordCollectionPageItems, d as InvalidPortableObjectIdError, f as classifyFep2277CoreType, ft as recordFanoutRecipients, g as preparePortableObjectProofPolicy, gt as recordWebFingerHandle, h as hasProofLike, ht as recordOutboxEnqueue, i as selectRequestSignature, it as instrumentDocumentLoader, jt as hasPortableActor, kt as getPortableDid, lt as recordCollectionRequest, m as getProofSigner, mt as recordOutboxActivity, n as listRequestSignatures, nt as getFederationMetrics, o as validateMaxSignatures, ot as recordCircuitBreakerStateChange, pt as recordInboxActivity, r as parseRfc9421SignatureInput, rt as getRemoteHost, s as verifyRequest, st as recordCollectionDispatchDuration, t as
|
|
6
|
-
import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-
|
|
7
|
-
import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "./portable-collection-
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
5
|
+
import { $ as getVerifiedKeyOwnerEvidence, A as compactJsonLd, At as getRawCanonicalPortableId, B as verifyCompactJsonLdDetailed, C as verifyPortableObjectProofWithRoot, Ct as observeAttempt, D as InvalidContextReferenceError, Dt as getCanonicalPortableId, E as normalizeOutgoingActivityJsonLd, Et as getAuthenticationOrigin, F as hasSignatureLike, Ft as isPortableUri, I as isClearlyMalformedContextReference, It as isSameObjectId, L as isInvalidUrlTypeError, M as detachSignature, Mt as isCompatibleKeyId, N as getNormalizationContextLoader, Nt as isPortableId, O as assertSafeJsonLd, Ot as getGatewayKeyBase, P as hasSignature, Pt as isPortableKeyId, Q as MAX_PORTABLE_KEY_TTL, R as markEffectiveContextLoader, Rt as name, S as verifyPortableObjectProofPolicy, Tt as verificationObservation, U as wrapContextLoaderForJsonLd, W as exportJwk, X as validateCryptoKey, Y as importJwk, _ as signObject, _t as registerQueueDepthGauge, at as isAbortError, b as verifyObjectDetailed, c as verifyRequestDetailed, ct as recordCollectionPageItems, d as InvalidPortableObjectIdError, f as classifyFep2277CoreType, ft as recordFanoutRecipients, g as preparePortableObjectProofPolicy, gt as recordWebFingerHandle, h as hasProofLike, ht as recordOutboxEnqueue, i as selectRequestSignature, it as instrumentDocumentLoader, jt as hasPortableActor, kt as getPortableDid, lt as recordCollectionRequest, m as getProofSigner, mt as recordOutboxActivity, n as listRequestSignatures, nt as getFederationMetrics, o as validateMaxSignatures, ot as recordCircuitBreakerStateChange, pt as recordInboxActivity, r as parseRfc9421SignatureInput, rt as getRemoteHost, s as verifyRequest, st as recordCollectionDispatchDuration, t as doubleKnockScoped, tt as getDurationMs, ut as recordCollectionTotalItems, v as verifyMapLocalProof, vt as preloadedOnlyDocumentLoader, w as verifyPreparedPortableObjectProofPolicy, wt as snapshotKey, x as verifyPortableObjectProof, y as verifyObject, yt as formatAcceptSignature, z as signJsonLd, zt as version } from "./http-ChXUU_3c.js";
|
|
6
|
+
import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-DUjZb4i6.js";
|
|
7
|
+
import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "./portable-collection-TmtpzD4E.js";
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DQcte7ES.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, PUBLIC_COLLECTION, Tombstone, getTypeId, isActor, lookupObject, lookupObserver, traverseCollection } from "@fedify/vocab";
|
|
12
12
|
import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
13
|
-
import { FetchError, UrlError, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDigestMultibase, parseHashlink, parseIri, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id
|
|
13
|
+
import { FetchError, UrlError, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDigestMultibase, parseHashlink, parseIri, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id } from "@fedify/vocab-runtime";
|
|
14
14
|
import { getPortableGatewayCandidates, isCompatibleEf61Iri } from "@fedify/vocab-runtime/internal/portable-dereference";
|
|
15
15
|
import { encodeHex } from "byte-encodings/hex";
|
|
16
16
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
17
17
|
import { uniq } from "es-toolkit";
|
|
18
18
|
import serialize from "json-canon";
|
|
19
19
|
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";
|
|
20
|
+
import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
|
|
20
21
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
21
22
|
import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
22
23
|
import { parse, stringifyAsync } from "devalue";
|
|
@@ -4315,9 +4316,10 @@ function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
|
|
|
4315
4316
|
*
|
|
4316
4317
|
* @param parameters The parameters for sending the activity.
|
|
4317
4318
|
* See also {@link SendActivityParameters}.
|
|
4319
|
+
* @param deps The dependencies, for tests.
|
|
4318
4320
|
* @throws {Error} If the activity fails to send.
|
|
4319
4321
|
*/
|
|
4320
|
-
function sendActivity(options) {
|
|
4322
|
+
function sendActivity(options, deps = {}) {
|
|
4321
4323
|
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
|
4322
4324
|
return tracerProvider.getTracer(name, version).startActiveSpan("activitypub.send_activity", {
|
|
4323
4325
|
kind: SpanKind.CLIENT,
|
|
@@ -4333,7 +4335,7 @@ function sendActivity(options) {
|
|
|
4333
4335
|
}, () => sendActivityInternal({
|
|
4334
4336
|
...options,
|
|
4335
4337
|
tracerProvider
|
|
4336
|
-
}, span));
|
|
4338
|
+
}, span, deps));
|
|
4337
4339
|
} catch (e) {
|
|
4338
4340
|
span.setStatus({
|
|
4339
4341
|
code: SpanStatusCode.ERROR,
|
|
@@ -4418,13 +4420,14 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
4418
4420
|
totalBytes += value.length;
|
|
4419
4421
|
}
|
|
4420
4422
|
} finally {
|
|
4423
|
+
if (truncated) reader.cancel().catch(() => {});
|
|
4421
4424
|
reader.releaseLock();
|
|
4422
4425
|
}
|
|
4423
4426
|
let result = chunks.join("");
|
|
4424
4427
|
if (truncated) result += "… (truncated)";
|
|
4425
4428
|
return result;
|
|
4426
4429
|
}
|
|
4427
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
4430
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span, deps) {
|
|
4428
4431
|
const logger = getLogger([
|
|
4429
4432
|
"fedify",
|
|
4430
4433
|
"federation",
|
|
@@ -4433,9 +4436,11 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
4433
4436
|
const federationMetrics = getFederationMetrics(meterProvider);
|
|
4434
4437
|
const started = performance.now();
|
|
4435
4438
|
let deliverySuccess = false;
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
+
const openUnvalidatedHop = deps.openHop ?? (allowPrivateAddress ? openDirectFetch : (url) => openPublicFetch(url));
|
|
4440
|
+
async function openHop(url) {
|
|
4441
|
+
let session;
|
|
4442
|
+
try {
|
|
4443
|
+
session = await openUnvalidatedHop(url);
|
|
4439
4444
|
} catch (error) {
|
|
4440
4445
|
if (error instanceof UrlError && error.reason === "dns") {
|
|
4441
4446
|
logger.error("DNS resolution failed for URL: {url}", {
|
|
@@ -4452,82 +4457,90 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
4452
4457
|
});
|
|
4453
4458
|
throw error;
|
|
4454
4459
|
}
|
|
4460
|
+
return session;
|
|
4455
4461
|
}
|
|
4462
|
+
let initialSession;
|
|
4456
4463
|
try {
|
|
4457
|
-
await
|
|
4464
|
+
initialSession = await openHop(inbox.href);
|
|
4458
4465
|
} catch (error) {
|
|
4459
4466
|
if (error instanceof FetchError) federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
4460
4467
|
throw error;
|
|
4461
4468
|
}
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
}
|
|
4470
|
-
let rsaKey = null;
|
|
4471
|
-
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
4472
|
-
rsaKey = key;
|
|
4473
|
-
break;
|
|
4474
|
-
}
|
|
4475
|
-
if (rsaKey == null && keys.length < 1) logger.debug("Sending the activity {activityId} to {inbox} without a signature.", {
|
|
4476
|
-
activityId,
|
|
4477
|
-
inbox: inbox.href
|
|
4478
|
-
});
|
|
4479
|
-
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.", {
|
|
4480
|
-
inbox: inbox.href,
|
|
4481
|
-
keys: keys.map((pair) => ({
|
|
4482
|
-
keyId: pair.keyId.href,
|
|
4483
|
-
privateKey: pair.privateKey
|
|
4484
|
-
}))
|
|
4485
|
-
});
|
|
4486
|
-
let response;
|
|
4469
|
+
let initialPending = true;
|
|
4470
|
+
const openDeliveryHop = (url) => {
|
|
4471
|
+
if (initialPending) {
|
|
4472
|
+
initialPending = false;
|
|
4473
|
+
return Promise.resolve(initialSession);
|
|
4474
|
+
}
|
|
4475
|
+
return openHop(url);
|
|
4476
|
+
};
|
|
4487
4477
|
try {
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4478
|
+
headers = new Headers(headers);
|
|
4479
|
+
headers.set("Content-Type", "application/activity+json");
|
|
4480
|
+
const request = new Request(inbox, {
|
|
4481
|
+
method: "POST",
|
|
4482
|
+
headers,
|
|
4483
|
+
body: JSON.stringify(activity),
|
|
4484
|
+
redirect: "manual"
|
|
4492
4485
|
});
|
|
4493
|
-
|
|
4494
|
-
const
|
|
4495
|
-
|
|
4486
|
+
let rsaKey = null;
|
|
4487
|
+
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
4488
|
+
rsaKey = key;
|
|
4489
|
+
break;
|
|
4490
|
+
}
|
|
4491
|
+
if (rsaKey == null && keys.length < 1) logger.debug("Sending the activity {activityId} to {inbox} without a signature.", {
|
|
4496
4492
|
activityId,
|
|
4493
|
+
inbox: inbox.href
|
|
4494
|
+
});
|
|
4495
|
+
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.", {
|
|
4497
4496
|
inbox: inbox.href,
|
|
4498
|
-
|
|
4497
|
+
keys: keys.map((pair) => ({
|
|
4498
|
+
keyId: pair.keyId.href,
|
|
4499
|
+
privateKey: pair.privateKey
|
|
4500
|
+
}))
|
|
4499
4501
|
});
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
}
|
|
4509
|
-
error = "";
|
|
4510
|
-
}
|
|
4511
|
-
logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
|
|
4502
|
+
let outcome;
|
|
4503
|
+
try {
|
|
4504
|
+
outcome = rsaKey == null ? await fetchWithValidatedRedirects(request, openDeliveryHop, (response) => consumeSendActivityResponse(response, inbox)) : await doubleKnockScoped(request, rsaKey, {
|
|
4505
|
+
tracerProvider,
|
|
4506
|
+
specDeterminer
|
|
4507
|
+
}, openDeliveryHop, (response) => consumeSendActivityResponse(response, inbox));
|
|
4508
|
+
} catch (error) {
|
|
4509
|
+
const failure = error instanceof FetchError || error instanceof UrlError ? error : createFetchError(inbox.href, error);
|
|
4510
|
+
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
4512
4511
|
activityId,
|
|
4513
4512
|
inbox: inbox.href,
|
|
4514
|
-
|
|
4515
|
-
statusText: response.statusText,
|
|
4516
|
-
error
|
|
4513
|
+
error: failure
|
|
4517
4514
|
});
|
|
4518
|
-
|
|
4515
|
+
federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
4516
|
+
throw failure;
|
|
4517
|
+
}
|
|
4518
|
+
try {
|
|
4519
|
+
if (!outcome.ok) {
|
|
4520
|
+
const { status, statusText, error, responseHeaders } = outcome;
|
|
4521
|
+
logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
|
|
4522
|
+
activityId,
|
|
4523
|
+
inbox: inbox.href,
|
|
4524
|
+
status,
|
|
4525
|
+
statusText,
|
|
4526
|
+
error
|
|
4527
|
+
});
|
|
4528
|
+
throw new SendActivityError(inbox, status, `Failed to send activity ${activityId} to ${inbox.href} (${status} ${statusText}):\n${error}`, error, responseHeaders);
|
|
4529
|
+
}
|
|
4530
|
+
deliverySuccess = true;
|
|
4531
|
+
const eventAttributes = {
|
|
4532
|
+
"activitypub.inbox.url": inbox.href,
|
|
4533
|
+
"activitypub.activity.id": activityId ?? ""
|
|
4534
|
+
};
|
|
4535
|
+
if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
|
|
4536
|
+
const actorId = getActivityActorId(activity);
|
|
4537
|
+
if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
|
|
4538
|
+
span.addEvent("activitypub.activity.sent", eventAttributes);
|
|
4539
|
+
} finally {
|
|
4540
|
+
federationMetrics.recordDelivery(inbox, getDurationMs(started), deliverySuccess, activityType);
|
|
4519
4541
|
}
|
|
4520
|
-
deliverySuccess = true;
|
|
4521
|
-
const eventAttributes = {
|
|
4522
|
-
"activitypub.inbox.url": inbox.href,
|
|
4523
|
-
"activitypub.activity.id": activityId ?? ""
|
|
4524
|
-
};
|
|
4525
|
-
if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
|
|
4526
|
-
const actorId = getActivityActorId(activity);
|
|
4527
|
-
if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
|
|
4528
|
-
span.addEvent("activitypub.activity.sent", eventAttributes);
|
|
4529
4542
|
} finally {
|
|
4530
|
-
|
|
4543
|
+
if (initialPending) await initialSession.close();
|
|
4531
4544
|
}
|
|
4532
4545
|
}
|
|
4533
4546
|
function createFetchError(url, cause) {
|
|
@@ -4535,10 +4548,75 @@ function createFetchError(url, cause) {
|
|
|
4535
4548
|
error.cause = cause;
|
|
4536
4549
|
return error;
|
|
4537
4550
|
}
|
|
4538
|
-
|
|
4551
|
+
/**
|
|
4552
|
+
* Starts cancelling the body of a response without waiting for it to settle.
|
|
4553
|
+
* The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
|
|
4554
|
+
* settles only after the other branch is cancelled too, per the Streams
|
|
4555
|
+
* standard, so awaiting it could block forever. Failures are only logged.
|
|
4556
|
+
*/
|
|
4557
|
+
function cancelResponseBody(response, url) {
|
|
4558
|
+
response.body?.cancel().catch((error) => {
|
|
4559
|
+
getLogger([
|
|
4560
|
+
"fedify",
|
|
4561
|
+
"federation",
|
|
4562
|
+
"outbox"
|
|
4563
|
+
]).debug("Failed to cancel the response body from {url}: {error}", {
|
|
4564
|
+
url,
|
|
4565
|
+
error
|
|
4566
|
+
});
|
|
4567
|
+
});
|
|
4568
|
+
}
|
|
4569
|
+
/**
|
|
4570
|
+
* Reads what is needed from the final response of a delivery while its
|
|
4571
|
+
* session is open: the body of a successful response is cancelled, and at
|
|
4572
|
+
* most 1 KiB of an error response body is read.
|
|
4573
|
+
*/
|
|
4574
|
+
async function consumeSendActivityResponse(response, inbox) {
|
|
4575
|
+
if (response.ok) {
|
|
4576
|
+
cancelResponseBody(response, response.url || inbox.href);
|
|
4577
|
+
return { ok: true };
|
|
4578
|
+
}
|
|
4579
|
+
let error;
|
|
4580
|
+
try {
|
|
4581
|
+
error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
|
|
4582
|
+
} catch (_) {
|
|
4583
|
+
error = "";
|
|
4584
|
+
}
|
|
4585
|
+
return {
|
|
4586
|
+
ok: false,
|
|
4587
|
+
status: response.status,
|
|
4588
|
+
statusText: response.statusText,
|
|
4589
|
+
error,
|
|
4590
|
+
responseHeaders: new Headers(response.headers)
|
|
4591
|
+
};
|
|
4592
|
+
}
|
|
4593
|
+
/**
|
|
4594
|
+
* Sends a request without signing it, following redirects manually like the
|
|
4595
|
+
* Fetch standard does. Every hop is opened with `openHop()`, which validates
|
|
4596
|
+
* the destination, after the previous hop is closed. The final response is
|
|
4597
|
+
* passed to `consume()`, and its session is closed after that.
|
|
4598
|
+
* This is exported only for tests; it is not exported from any entry point.
|
|
4599
|
+
* @param request The request to send.
|
|
4600
|
+
* @param openHop The function to open a session for each hop.
|
|
4601
|
+
* @param consume The function to consume the final response.
|
|
4602
|
+
* @returns The value returned by `consume`.
|
|
4603
|
+
*/
|
|
4604
|
+
async function fetchWithValidatedRedirects(request, openHop, consume) {
|
|
4605
|
+
const logger = getLogger([
|
|
4606
|
+
"fedify",
|
|
4607
|
+
"federation",
|
|
4608
|
+
"outbox"
|
|
4609
|
+
]);
|
|
4539
4610
|
let body = await request.clone().text();
|
|
4540
4611
|
for (let redirects = 0;; redirects++) {
|
|
4541
|
-
const
|
|
4612
|
+
const session = await openHop(request.url);
|
|
4613
|
+
let response;
|
|
4614
|
+
try {
|
|
4615
|
+
response = await session.fetch(request, { redirect: "manual" });
|
|
4616
|
+
} catch (error) {
|
|
4617
|
+
await closeAfterError(session, logger);
|
|
4618
|
+
throw error;
|
|
4619
|
+
}
|
|
4542
4620
|
const location = response.headers.get("Location");
|
|
4543
4621
|
if (![
|
|
4544
4622
|
301,
|
|
@@ -4546,11 +4624,21 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
|
4546
4624
|
303,
|
|
4547
4625
|
307,
|
|
4548
4626
|
308
|
|
4549
|
-
].includes(response.status) || location == null)
|
|
4550
|
-
|
|
4627
|
+
].includes(response.status) || location == null) try {
|
|
4628
|
+
return await consume(response);
|
|
4629
|
+
} catch (error) {
|
|
4630
|
+
logger.debug("Failed to consume the response from {url}; closing the session: {error}", {
|
|
4631
|
+
url: request.url,
|
|
4632
|
+
error
|
|
4633
|
+
});
|
|
4634
|
+
throw error;
|
|
4635
|
+
} finally {
|
|
4636
|
+
await session.close();
|
|
4637
|
+
}
|
|
4638
|
+
cancelResponseBody(response, request.url);
|
|
4639
|
+
await session.close();
|
|
4551
4640
|
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
4552
4641
|
const url = new URL(location, request.url);
|
|
4553
|
-
await validateUrl(url.href);
|
|
4554
4642
|
const headers = new Headers(request.headers);
|
|
4555
4643
|
if (url.origin !== new URL(request.url).origin) {
|
|
4556
4644
|
headers.delete("Authorization");
|
|
@@ -4578,6 +4666,16 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
|
4578
4666
|
});
|
|
4579
4667
|
}
|
|
4580
4668
|
}
|
|
4669
|
+
async function closeAfterError(session, logger) {
|
|
4670
|
+
try {
|
|
4671
|
+
await session.close();
|
|
4672
|
+
} catch (error) {
|
|
4673
|
+
logger.debug("Failed to close the session for {url}: {error}", {
|
|
4674
|
+
url: session.url.href,
|
|
4675
|
+
error
|
|
4676
|
+
});
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4581
4679
|
/**
|
|
4582
4680
|
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
4583
4681
|
* It contains structured information about the failure, including the HTTP
|
|
@@ -10804,7 +10902,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
10804
10902
|
onNotAcceptable ??= notAcceptable;
|
|
10805
10903
|
onUnauthorized ??= unauthorized;
|
|
10806
10904
|
const url = new URL(request.url);
|
|
10807
|
-
const route = this.router.route(url.pathname);
|
|
10905
|
+
const route = this.router.route(url.pathname, { allowUnencodedColon: true });
|
|
10808
10906
|
if (route == null) {
|
|
10809
10907
|
const media = this.hashlinkMediaDispatcher == null ? null : parseHashlinkGatewayRequest(url);
|
|
10810
10908
|
if (media != null) return await this.#fetchHashlinkMedia(request, media, {
|
|
@@ -12623,52 +12721,92 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
12623
12721
|
if (object instanceof Tombstone && !(object instanceof cls) && options?.tombstone !== "passthrough") return null;
|
|
12624
12722
|
return object;
|
|
12625
12723
|
}
|
|
12626
|
-
#signedKey =
|
|
12724
|
+
#signedKey = null;
|
|
12725
|
+
#signedKeyOwner = null;
|
|
12726
|
+
#signatureChecks = /* @__PURE__ */ new WeakMap();
|
|
12727
|
+
#getSignatureCheck(documentLoader, contextLoader) {
|
|
12728
|
+
let checks = this.#signatureChecks.get(documentLoader);
|
|
12729
|
+
if (checks == null) {
|
|
12730
|
+
checks = /* @__PURE__ */ new WeakMap();
|
|
12731
|
+
this.#signatureChecks.set(documentLoader, checks);
|
|
12732
|
+
}
|
|
12733
|
+
let check = checks.get(contextLoader);
|
|
12734
|
+
if (check == null) {
|
|
12735
|
+
check = {};
|
|
12736
|
+
checks.set(contextLoader, check);
|
|
12737
|
+
}
|
|
12738
|
+
return check;
|
|
12739
|
+
}
|
|
12627
12740
|
async getSignedKey(options = {}) {
|
|
12628
12741
|
if (this.#signedKey != null) return this.#signedKey;
|
|
12629
|
-
const
|
|
12630
|
-
|
|
12631
|
-
|
|
12742
|
+
const documentLoader = options.documentLoader ?? this.documentLoader;
|
|
12743
|
+
const contextLoader = options.contextLoader ?? this.contextLoader;
|
|
12744
|
+
const check = this.#getSignatureCheck(documentLoader, contextLoader);
|
|
12745
|
+
if (check.key != null) return check.key;
|
|
12746
|
+
const keyCache = documentLoader === this.documentLoader && contextLoader === this.contextLoader ? new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
|
|
12747
|
+
documentLoader,
|
|
12748
|
+
contextLoader,
|
|
12632
12749
|
tracerProvider: options.tracerProvider ?? this.tracerProvider,
|
|
12633
12750
|
keyTtl: this.federation.publicKeyTtl
|
|
12634
|
-
});
|
|
12635
|
-
|
|
12751
|
+
}) : void 0;
|
|
12752
|
+
const promise = verifyRequest(this.request, {
|
|
12636
12753
|
...this,
|
|
12637
|
-
contextLoader
|
|
12638
|
-
documentLoader
|
|
12754
|
+
contextLoader,
|
|
12755
|
+
documentLoader,
|
|
12639
12756
|
timeWindow: this.federation.signatureTimeWindow,
|
|
12640
12757
|
maxSignatures: this.federation.maxHttpSignatures,
|
|
12758
|
+
keyCache,
|
|
12641
12759
|
meterProvider: this.meterProvider,
|
|
12642
|
-
tracerProvider: options.tracerProvider ?? this.tracerProvider
|
|
12643
|
-
|
|
12760
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider
|
|
12761
|
+
}).then((key) => {
|
|
12762
|
+
if (key != null) this.#signedKey ??= key;
|
|
12763
|
+
return key;
|
|
12764
|
+
}).catch((error) => {
|
|
12765
|
+
if (check.key === promise) check.key = void 0;
|
|
12766
|
+
throw error;
|
|
12644
12767
|
});
|
|
12768
|
+
check.key = promise;
|
|
12769
|
+
return await promise;
|
|
12645
12770
|
}
|
|
12646
|
-
#signedKeyOwner = void 0;
|
|
12647
12771
|
async getSignedKeyOwner(options = {}) {
|
|
12648
12772
|
if (this.#signedKeyOwner != null) return this.#signedKeyOwner;
|
|
12649
|
-
const
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
"fedify",
|
|
12661
|
-
"federation",
|
|
12662
|
-
"actor"
|
|
12663
|
-
]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
|
|
12664
|
-
keyId: key.id?.href,
|
|
12665
|
-
keyOwner: error.url.href,
|
|
12666
|
-
error
|
|
12667
|
-
});
|
|
12668
|
-
return this.#signedKeyOwner = null;
|
|
12773
|
+
const normalizedOptions = {
|
|
12774
|
+
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
12775
|
+
contextLoader: options.contextLoader ?? this.contextLoader,
|
|
12776
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider
|
|
12777
|
+
};
|
|
12778
|
+
const check = this.#getSignatureCheck(normalizedOptions.documentLoader, normalizedOptions.contextLoader);
|
|
12779
|
+
if (check.owner != null) return check.owner;
|
|
12780
|
+
const promise = this.getSignedKey(normalizedOptions).then(async (key) => {
|
|
12781
|
+
if (key == null) {
|
|
12782
|
+
if (check.owner === promise) check.owner = void 0;
|
|
12783
|
+
return null;
|
|
12669
12784
|
}
|
|
12785
|
+
try {
|
|
12786
|
+
const owner = await getKeyOwner(key, normalizedOptions);
|
|
12787
|
+
if (owner != null) this.#signedKeyOwner ??= owner;
|
|
12788
|
+
return owner;
|
|
12789
|
+
} catch (error) {
|
|
12790
|
+
if (error instanceof FetchError) {
|
|
12791
|
+
getLogger([
|
|
12792
|
+
"fedify",
|
|
12793
|
+
"federation",
|
|
12794
|
+
"actor"
|
|
12795
|
+
]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
|
|
12796
|
+
keyId: key.id?.href,
|
|
12797
|
+
keyOwner: error.url.href,
|
|
12798
|
+
error
|
|
12799
|
+
});
|
|
12800
|
+
return null;
|
|
12801
|
+
}
|
|
12802
|
+
throw error;
|
|
12803
|
+
}
|
|
12804
|
+
}).catch((error) => {
|
|
12805
|
+
if (check.owner === promise) check.owner = void 0;
|
|
12670
12806
|
throw error;
|
|
12671
|
-
}
|
|
12807
|
+
});
|
|
12808
|
+
check.owner = promise;
|
|
12809
|
+
return await promise;
|
|
12672
12810
|
}
|
|
12673
12811
|
async isSignedByAudience(object, options = {}) {
|
|
12674
12812
|
return await isInAudience(object, null) || await isInAudience(object, await this.getSignedKeyOwner(options), options);
|