@fedify/fedify 2.4.5-dev.2462 → 2.4.6-dev.2628
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-BBUGd9wr.mjs → builder-kj5boG2l.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-CariICma.mjs → compound-proof-BoNAYSHt.mjs} +2 -2
- package/dist/{context-7NyiqIcg.d.ts → context-CVOOd9pD.d.ts} +28 -2
- package/dist/{context-tsYPJ2Ew.d.cts → context-D0gFIgev.d.cts} +28 -2
- package/dist/{deno-BO7-a3hz.mjs → deno-ksnVeCA7.mjs} +1 -1
- package/dist/docloader-BhVNi06k.mjs +136 -0
- 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 +4 -4
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +465 -32
- 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/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 +498 -88
- 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-Dd9IwOEi.cjs → federation-BtPz9sr3.cjs} +240 -102
- package/dist/{http-D7L2pqLT.mjs → http-BSARoDPh.mjs} +204 -106
- package/dist/{http-DLf8QYD6.cjs → http-CeVcKQMZ.cjs} +179 -110
- package/dist/{http-BVrOR6MG.js → http-rH230xCD.js} +178 -109
- package/dist/{inbox-DtEKOr3W.mjs → inbox-BdZenpZQ.mjs} +2 -2
- package/dist/{key-DYxuyd-s.d.cts → key-CyCcut3h.d.ts} +1 -2
- package/dist/{key-C1nv7_jD.d.ts → key-VEL1QqHF.d.cts} +1 -2
- package/dist/{kv-cache-Dkzuqs8B.cjs → kv-cache-BpGIONmd.cjs} +72 -27
- package/dist/{kv-cache-DoZoRS9p.js → kv-cache-CNfNI_95.js} +73 -28
- package/dist/{kv-cache-m3RIr811.mjs → kv-cache-Cxn7pNMx.mjs} +1 -1
- package/dist/{ld-PJLGA2MJ.mjs → ld-DJZrjNuN.mjs} +2 -2
- package/dist/{metrics-CGR9cI-Y.mjs → metrics-DtxsYIkN.mjs} +1 -1
- package/dist/{middleware-BxhBd7pw.js → middleware-DDvck2q9.js} +241 -103
- package/dist/{middleware-ByanhTvb.mjs → middleware-DblGj642.mjs} +82 -42
- package/dist/{middleware-DPI-Jp9w.mjs → middleware-DyEjt33n.mjs} +1 -1
- package/dist/{mod-DEZXwV6J.d.ts → mod-B1Qt5cQQ.d.ts} +2 -9
- package/dist/{mod-Xj5P4s4d.d.ts → mod-B5COx5Wd.d.ts} +1 -1
- package/dist/{mod-CavhAgLw.d.cts → mod-BTxJG7ZH.d.cts} +1 -1
- package/dist/{mod-CtpF2BU5.d.ts → mod-CpyjFA9s.d.ts} +1 -1
- package/dist/{mod-BojP2Bw3.d.cts → mod-makPMVMg.d.cts} +1 -1
- package/dist/{mod-Dw-nPxQh.d.cts → mod-ss0wBUH1.d.cts} +2 -9
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +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 +292 -30
- 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-B5c276hH.mjs → owner-2vfqhUJw.mjs} +3 -3
- package/dist/{portable-collection-BMZY9qT-.js → portable-collection-B7AQ_dW7.js} +1 -1
- package/dist/{portable-collection-CR5uwjws.cjs → portable-collection-Cs--KD5A.cjs} +1 -1
- package/dist/{portable-collection-C88tBiy0.mjs → portable-collection-DKJwGO8S.mjs} +2 -2
- package/dist/{portable-key-DxTXUpCK.mjs → portable-key-DXGepvtD.mjs} +1 -1
- package/dist/{proof-DNu-qP0i.mjs → proof-DW0FK6Cp.mjs} +3 -3
- package/dist/{send-CdCifQs4.mjs → send-CqsocpvF.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 +2 -2
- package/dist/sig/mod.d.ts +2 -2
- 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-BuMu2bgP.mjs → temporal-Dm2E1-vs.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 +7 -7
- package/dist/docloader-DQNRDYDb.mjs +0 -91
|
@@ -3,8 +3,8 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { c as isPortableActorDocument, d as isPortableUri, l as isPortableId, m as parseKeyIdString, s as isCompatibleKeyId } 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 { _ as recordKeyLookup, n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch, t as classifyFetchError } from "./metrics-
|
|
6
|
+
import { n as version, t as name } from "./deno-ksnVeCA7.mjs";
|
|
7
|
+
import { _ as recordKeyLookup, n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch, t as classifyFetchError } from "./metrics-DtxsYIkN.mjs";
|
|
8
8
|
import { n as attachKeyOwnerEvidence, t as MAX_PORTABLE_KEY_TTL } from "./key-owner-evidence-APB6NpSk.mjs";
|
|
9
9
|
import { getLogger } from "@logtape/logtape";
|
|
10
10
|
import { Activity, CryptographicKey, Multikey, Object as Object$1, getTypeId, isActor } from "@fedify/vocab";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
4
|
+
import { n as version, t as name } from "./deno-ksnVeCA7.mjs";
|
|
5
5
|
import { metrics } from "@opentelemetry/api";
|
|
6
6
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/federation/metrics.ts
|
|
@@ -2,20 +2,21 @@ 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 instrumentDocumentLoader, A as hasSignature, At as isPortableUri, C as InvalidContextReferenceError, Ct as getGatewayKeyBase, Dt as isCompatibleKeyId, E as compactJsonLd, Et as hasPortableActor, F as verifyCompactJsonLd, G as validateCryptoKey, M as isClearlyMalformedContextReference, N as isInvalidUrlTypeError, Nt as name, O as detachSignature, Ot as isPortableId, P as signJsonLd, Pt as version, Q as getRemoteHost, R as wrapContextLoaderForJsonLd, S as normalizeOutgoingActivityJsonLd, St as getCanonicalPortableId, Tt as getRawCanonicalPortableId, W as importJwk, X as getDurationMs, Z as getFederationMetrics, _ as verifyObject, at as recordCollectionTotalItems, b as verifyPortableObjectProofWithRoot, bt as verificationObservation, c as verifyRequestDetailed, ct as recordInboxActivity, d as InvalidPortableObjectIdError, dt as recordWebFingerHandle, et as isAbortError, f as classifyFep2277CoreType, ft as registerQueueDepthGauge, g as verifyMapLocalProof, h as signObject, i as selectRequestSignature, it as recordCollectionRequest, j as hasSignatureLike, jt as isSameObjectId, k as getNormalizationContextLoader, kt as isPortableKeyId, lt as recordOutboxActivity, m as hasProofLike, mt as formatAcceptSignature, n as listRequestSignatures, nt as recordCollectionDispatchDuration, o as validateMaxSignatures, pt as preloadedOnlyDocumentLoader, q as MAX_PORTABLE_KEY_TTL, r as parseRfc9421SignatureInput, rt as recordCollectionPageItems, s as verifyRequest, st as recordFanoutRecipients, 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 instrumentDocumentLoader, A as hasSignature, At as isPortableUri, C as InvalidContextReferenceError, Ct as getGatewayKeyBase, Dt as isCompatibleKeyId, E as compactJsonLd, Et as hasPortableActor, F as verifyCompactJsonLd, G as validateCryptoKey, M as isClearlyMalformedContextReference, N as isInvalidUrlTypeError, Nt as name, O as detachSignature, Ot as isPortableId, P as signJsonLd, Pt as version, Q as getRemoteHost, R as wrapContextLoaderForJsonLd, S as normalizeOutgoingActivityJsonLd, St as getCanonicalPortableId, Tt as getRawCanonicalPortableId, W as importJwk, X as getDurationMs, Z as getFederationMetrics, _ as verifyObject, at as recordCollectionTotalItems, b as verifyPortableObjectProofWithRoot, bt as verificationObservation, c as verifyRequestDetailed, ct as recordInboxActivity, d as InvalidPortableObjectIdError, dt as recordWebFingerHandle, et as isAbortError, f as classifyFep2277CoreType, ft as registerQueueDepthGauge, g as verifyMapLocalProof, h as signObject, i as selectRequestSignature, it as recordCollectionRequest, j as hasSignatureLike, jt as isSameObjectId, k as getNormalizationContextLoader, kt as isPortableKeyId, lt as recordOutboxActivity, m as hasProofLike, mt as formatAcceptSignature, n as listRequestSignatures, nt as recordCollectionDispatchDuration, o as validateMaxSignatures, pt as preloadedOnlyDocumentLoader, q as MAX_PORTABLE_KEY_TTL, r as parseRfc9421SignatureInput, rt as recordCollectionPageItems, s as verifyRequest, st as recordFanoutRecipients, t as doubleKnockScoped, tt as recordCircuitBreakerStateChange, ut as recordOutboxEnqueue, v as verifyPortableObjectProof, vt as observeAttempt, w as assertSafeJsonLd, wt as getPortableDid, xt as getAuthenticationOrigin, y as verifyPortableObjectProofPolicy, yt as snapshotKey, z as exportJwk } from "./http-rH230xCD.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-B7AQ_dW7.js";
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CNfNI_95.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, 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 { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
|
|
19
|
+
import { openDirectFetch, openPublicFetch } from "@fedify/vocab-runtime/internal";
|
|
19
20
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
20
21
|
import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
21
22
|
import { parse, stringifyAsync } from "devalue";
|
|
@@ -3617,9 +3618,10 @@ function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris }) {
|
|
|
3617
3618
|
*
|
|
3618
3619
|
* @param parameters The parameters for sending the activity.
|
|
3619
3620
|
* See also {@link SendActivityParameters}.
|
|
3621
|
+
* @param deps The dependencies, for tests.
|
|
3620
3622
|
* @throws {Error} If the activity fails to send.
|
|
3621
3623
|
*/
|
|
3622
|
-
function sendActivity(options) {
|
|
3624
|
+
function sendActivity(options, deps = {}) {
|
|
3623
3625
|
const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
|
|
3624
3626
|
return tracerProvider.getTracer(name, version).startActiveSpan("activitypub.send_activity", {
|
|
3625
3627
|
kind: SpanKind.CLIENT,
|
|
@@ -3631,7 +3633,7 @@ function sendActivity(options) {
|
|
|
3631
3633
|
await sendActivityInternal({
|
|
3632
3634
|
...options,
|
|
3633
3635
|
tracerProvider
|
|
3634
|
-
}, span);
|
|
3636
|
+
}, span, deps);
|
|
3635
3637
|
} catch (e) {
|
|
3636
3638
|
span.setStatus({
|
|
3637
3639
|
code: SpanStatusCode.ERROR,
|
|
@@ -3716,13 +3718,14 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
3716
3718
|
totalBytes += value.length;
|
|
3717
3719
|
}
|
|
3718
3720
|
} finally {
|
|
3721
|
+
if (truncated) reader.cancel().catch(() => {});
|
|
3719
3722
|
reader.releaseLock();
|
|
3720
3723
|
}
|
|
3721
3724
|
let result = chunks.join("");
|
|
3722
3725
|
if (truncated) result += "… (truncated)";
|
|
3723
3726
|
return result;
|
|
3724
3727
|
}
|
|
3725
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
3728
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span, deps) {
|
|
3726
3729
|
const logger = getLogger([
|
|
3727
3730
|
"fedify",
|
|
3728
3731
|
"federation",
|
|
@@ -3731,9 +3734,11 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
3731
3734
|
const federationMetrics = getFederationMetrics(meterProvider);
|
|
3732
3735
|
const started = performance.now();
|
|
3733
3736
|
let deliverySuccess = false;
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
+
const openUnvalidatedHop = deps.openHop ?? (allowPrivateAddress ? openDirectFetch : (url) => openPublicFetch(url));
|
|
3738
|
+
async function openHop(url) {
|
|
3739
|
+
let session;
|
|
3740
|
+
try {
|
|
3741
|
+
session = await openUnvalidatedHop(url);
|
|
3737
3742
|
} catch (error) {
|
|
3738
3743
|
if (error instanceof UrlError && error.reason === "dns") {
|
|
3739
3744
|
logger.error("DNS resolution failed for URL: {url}", {
|
|
@@ -3750,82 +3755,90 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
3750
3755
|
});
|
|
3751
3756
|
throw error;
|
|
3752
3757
|
}
|
|
3758
|
+
return session;
|
|
3753
3759
|
}
|
|
3760
|
+
let initialSession;
|
|
3754
3761
|
try {
|
|
3755
|
-
await
|
|
3762
|
+
initialSession = await openHop(inbox.href);
|
|
3756
3763
|
} catch (error) {
|
|
3757
3764
|
if (error instanceof FetchError) federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
3758
3765
|
throw error;
|
|
3759
3766
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
}
|
|
3768
|
-
let rsaKey = null;
|
|
3769
|
-
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
3770
|
-
rsaKey = key;
|
|
3771
|
-
break;
|
|
3772
|
-
}
|
|
3773
|
-
if (rsaKey == null && keys.length < 1) logger.debug("Sending the activity {activityId} to {inbox} without a signature.", {
|
|
3774
|
-
activityId,
|
|
3775
|
-
inbox: inbox.href
|
|
3776
|
-
});
|
|
3777
|
-
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.", {
|
|
3778
|
-
inbox: inbox.href,
|
|
3779
|
-
keys: keys.map((pair) => ({
|
|
3780
|
-
keyId: pair.keyId.href,
|
|
3781
|
-
privateKey: pair.privateKey
|
|
3782
|
-
}))
|
|
3783
|
-
});
|
|
3784
|
-
let response;
|
|
3767
|
+
let initialPending = true;
|
|
3768
|
+
const openDeliveryHop = (url) => {
|
|
3769
|
+
if (initialPending) {
|
|
3770
|
+
initialPending = false;
|
|
3771
|
+
return Promise.resolve(initialSession);
|
|
3772
|
+
}
|
|
3773
|
+
return openHop(url);
|
|
3774
|
+
};
|
|
3785
3775
|
try {
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3776
|
+
headers = new Headers(headers);
|
|
3777
|
+
headers.set("Content-Type", "application/activity+json");
|
|
3778
|
+
const request = new Request(inbox, {
|
|
3779
|
+
method: "POST",
|
|
3780
|
+
headers,
|
|
3781
|
+
body: JSON.stringify(activity),
|
|
3782
|
+
redirect: "manual"
|
|
3790
3783
|
});
|
|
3791
|
-
|
|
3792
|
-
const
|
|
3793
|
-
|
|
3784
|
+
let rsaKey = null;
|
|
3785
|
+
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
3786
|
+
rsaKey = key;
|
|
3787
|
+
break;
|
|
3788
|
+
}
|
|
3789
|
+
if (rsaKey == null && keys.length < 1) logger.debug("Sending the activity {activityId} to {inbox} without a signature.", {
|
|
3794
3790
|
activityId,
|
|
3791
|
+
inbox: inbox.href
|
|
3792
|
+
});
|
|
3793
|
+
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.", {
|
|
3795
3794
|
inbox: inbox.href,
|
|
3796
|
-
|
|
3795
|
+
keys: keys.map((pair) => ({
|
|
3796
|
+
keyId: pair.keyId.href,
|
|
3797
|
+
privateKey: pair.privateKey
|
|
3798
|
+
}))
|
|
3797
3799
|
});
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
}
|
|
3807
|
-
error = "";
|
|
3808
|
-
}
|
|
3809
|
-
logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
|
|
3800
|
+
let outcome;
|
|
3801
|
+
try {
|
|
3802
|
+
outcome = rsaKey == null ? await fetchWithValidatedRedirects(request, openDeliveryHop, (response) => consumeSendActivityResponse(response, inbox)) : await doubleKnockScoped(request, rsaKey, {
|
|
3803
|
+
tracerProvider,
|
|
3804
|
+
specDeterminer
|
|
3805
|
+
}, openDeliveryHop, (response) => consumeSendActivityResponse(response, inbox));
|
|
3806
|
+
} catch (error) {
|
|
3807
|
+
const failure = error instanceof FetchError || error instanceof UrlError ? error : createFetchError(inbox.href, error);
|
|
3808
|
+
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
3810
3809
|
activityId,
|
|
3811
3810
|
inbox: inbox.href,
|
|
3812
|
-
|
|
3813
|
-
statusText: response.statusText,
|
|
3814
|
-
error
|
|
3811
|
+
error: failure
|
|
3815
3812
|
});
|
|
3816
|
-
|
|
3813
|
+
federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
|
|
3814
|
+
throw failure;
|
|
3815
|
+
}
|
|
3816
|
+
try {
|
|
3817
|
+
if (!outcome.ok) {
|
|
3818
|
+
const { status, statusText, error, responseHeaders } = outcome;
|
|
3819
|
+
logger.error("Failed to send activity {activityId} to {inbox} ({status} {statusText}):\n{error}", {
|
|
3820
|
+
activityId,
|
|
3821
|
+
inbox: inbox.href,
|
|
3822
|
+
status,
|
|
3823
|
+
statusText,
|
|
3824
|
+
error
|
|
3825
|
+
});
|
|
3826
|
+
throw new SendActivityError(inbox, status, `Failed to send activity ${activityId} to ${inbox.href} (${status} ${statusText}):\n${error}`, error, responseHeaders);
|
|
3827
|
+
}
|
|
3828
|
+
deliverySuccess = true;
|
|
3829
|
+
const eventAttributes = {
|
|
3830
|
+
"activitypub.inbox.url": inbox.href,
|
|
3831
|
+
"activitypub.activity.id": activityId ?? ""
|
|
3832
|
+
};
|
|
3833
|
+
if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
|
|
3834
|
+
const actorId = getActivityActorId(activity);
|
|
3835
|
+
if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
|
|
3836
|
+
span.addEvent("activitypub.activity.sent", eventAttributes);
|
|
3837
|
+
} finally {
|
|
3838
|
+
federationMetrics.recordDelivery(inbox, getDurationMs(started), deliverySuccess, activityType);
|
|
3817
3839
|
}
|
|
3818
|
-
deliverySuccess = true;
|
|
3819
|
-
const eventAttributes = {
|
|
3820
|
-
"activitypub.inbox.url": inbox.href,
|
|
3821
|
-
"activitypub.activity.id": activityId ?? ""
|
|
3822
|
-
};
|
|
3823
|
-
if (activityType != null) eventAttributes["activitypub.activity.type"] = activityType;
|
|
3824
|
-
const actorId = getActivityActorId(activity);
|
|
3825
|
-
if (actorId != null) eventAttributes["activitypub.actor.id"] = actorId;
|
|
3826
|
-
span.addEvent("activitypub.activity.sent", eventAttributes);
|
|
3827
3840
|
} finally {
|
|
3828
|
-
|
|
3841
|
+
if (initialPending) await initialSession.close();
|
|
3829
3842
|
}
|
|
3830
3843
|
}
|
|
3831
3844
|
function createFetchError(url, cause) {
|
|
@@ -3833,10 +3846,75 @@ function createFetchError(url, cause) {
|
|
|
3833
3846
|
error.cause = cause;
|
|
3834
3847
|
return error;
|
|
3835
3848
|
}
|
|
3836
|
-
|
|
3849
|
+
/**
|
|
3850
|
+
* Starts cancelling the body of a response without waiting for it to settle.
|
|
3851
|
+
* The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
|
|
3852
|
+
* settles only after the other branch is cancelled too, per the Streams
|
|
3853
|
+
* standard, so awaiting it could block forever. Failures are only logged.
|
|
3854
|
+
*/
|
|
3855
|
+
function cancelResponseBody(response, url) {
|
|
3856
|
+
response.body?.cancel().catch((error) => {
|
|
3857
|
+
getLogger([
|
|
3858
|
+
"fedify",
|
|
3859
|
+
"federation",
|
|
3860
|
+
"outbox"
|
|
3861
|
+
]).debug("Failed to cancel the response body from {url}: {error}", {
|
|
3862
|
+
url,
|
|
3863
|
+
error
|
|
3864
|
+
});
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
/**
|
|
3868
|
+
* Reads what is needed from the final response of a delivery while its
|
|
3869
|
+
* session is open: the body of a successful response is cancelled, and at
|
|
3870
|
+
* most 1 KiB of an error response body is read.
|
|
3871
|
+
*/
|
|
3872
|
+
async function consumeSendActivityResponse(response, inbox) {
|
|
3873
|
+
if (response.ok) {
|
|
3874
|
+
cancelResponseBody(response, response.url || inbox.href);
|
|
3875
|
+
return { ok: true };
|
|
3876
|
+
}
|
|
3877
|
+
let error;
|
|
3878
|
+
try {
|
|
3879
|
+
error = await readLimitedResponseBody(response, MAX_ERROR_RESPONSE_BODY_BYTES);
|
|
3880
|
+
} catch (_) {
|
|
3881
|
+
error = "";
|
|
3882
|
+
}
|
|
3883
|
+
return {
|
|
3884
|
+
ok: false,
|
|
3885
|
+
status: response.status,
|
|
3886
|
+
statusText: response.statusText,
|
|
3887
|
+
error,
|
|
3888
|
+
responseHeaders: new Headers(response.headers)
|
|
3889
|
+
};
|
|
3890
|
+
}
|
|
3891
|
+
/**
|
|
3892
|
+
* Sends a request without signing it, following redirects manually like the
|
|
3893
|
+
* Fetch standard does. Every hop is opened with `openHop()`, which validates
|
|
3894
|
+
* the destination, after the previous hop is closed. The final response is
|
|
3895
|
+
* passed to `consume()`, and its session is closed after that.
|
|
3896
|
+
* This is exported only for tests; it is not exported from any entry point.
|
|
3897
|
+
* @param request The request to send.
|
|
3898
|
+
* @param openHop The function to open a session for each hop.
|
|
3899
|
+
* @param consume The function to consume the final response.
|
|
3900
|
+
* @returns The value returned by `consume`.
|
|
3901
|
+
*/
|
|
3902
|
+
async function fetchWithValidatedRedirects(request, openHop, consume) {
|
|
3903
|
+
const logger = getLogger([
|
|
3904
|
+
"fedify",
|
|
3905
|
+
"federation",
|
|
3906
|
+
"outbox"
|
|
3907
|
+
]);
|
|
3837
3908
|
let body = await request.clone().text();
|
|
3838
3909
|
for (let redirects = 0;; redirects++) {
|
|
3839
|
-
const
|
|
3910
|
+
const session = await openHop(request.url);
|
|
3911
|
+
let response;
|
|
3912
|
+
try {
|
|
3913
|
+
response = await session.fetch(request, { redirect: "manual" });
|
|
3914
|
+
} catch (error) {
|
|
3915
|
+
await closeAfterError(session, logger);
|
|
3916
|
+
throw error;
|
|
3917
|
+
}
|
|
3840
3918
|
const location = response.headers.get("Location");
|
|
3841
3919
|
if (![
|
|
3842
3920
|
301,
|
|
@@ -3844,11 +3922,21 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
|
3844
3922
|
303,
|
|
3845
3923
|
307,
|
|
3846
3924
|
308
|
|
3847
|
-
].includes(response.status) || location == null)
|
|
3848
|
-
|
|
3925
|
+
].includes(response.status) || location == null) try {
|
|
3926
|
+
return await consume(response);
|
|
3927
|
+
} catch (error) {
|
|
3928
|
+
logger.debug("Failed to consume the response from {url}; closing the session: {error}", {
|
|
3929
|
+
url: request.url,
|
|
3930
|
+
error
|
|
3931
|
+
});
|
|
3932
|
+
throw error;
|
|
3933
|
+
} finally {
|
|
3934
|
+
await session.close();
|
|
3935
|
+
}
|
|
3936
|
+
cancelResponseBody(response, request.url);
|
|
3937
|
+
await session.close();
|
|
3849
3938
|
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
3850
3939
|
const url = new URL(location, request.url);
|
|
3851
|
-
await validateUrl(url.href);
|
|
3852
3940
|
const headers = new Headers(request.headers);
|
|
3853
3941
|
if (url.origin !== new URL(request.url).origin) {
|
|
3854
3942
|
headers.delete("Authorization");
|
|
@@ -3876,6 +3964,16 @@ async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
|
3876
3964
|
});
|
|
3877
3965
|
}
|
|
3878
3966
|
}
|
|
3967
|
+
async function closeAfterError(session, logger) {
|
|
3968
|
+
try {
|
|
3969
|
+
await session.close();
|
|
3970
|
+
} catch (error) {
|
|
3971
|
+
logger.debug("Failed to close the session for {url}: {error}", {
|
|
3972
|
+
url: session.url.href,
|
|
3973
|
+
error
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3879
3977
|
/**
|
|
3880
3978
|
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
3881
3979
|
* It contains structured information about the failure, including the HTTP
|
|
@@ -9823,7 +9921,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
9823
9921
|
onNotAcceptable ??= notAcceptable;
|
|
9824
9922
|
onUnauthorized ??= unauthorized;
|
|
9825
9923
|
const url = new URL(request.url);
|
|
9826
|
-
const route = this.router.route(url.pathname);
|
|
9924
|
+
const route = this.router.route(url.pathname, { allowUnencodedColon: true });
|
|
9827
9925
|
if (route == null) {
|
|
9828
9926
|
const media = this.hashlinkMediaDispatcher == null ? null : parseHashlinkGatewayRequest(url);
|
|
9829
9927
|
if (media != null) return await this.#fetchHashlinkMedia(request, media, {
|
|
@@ -11556,45 +11654,85 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
11556
11654
|
if (object instanceof Tombstone && !(object instanceof cls) && options?.tombstone !== "passthrough") return null;
|
|
11557
11655
|
return object;
|
|
11558
11656
|
}
|
|
11559
|
-
#signedKey =
|
|
11657
|
+
#signedKey = null;
|
|
11658
|
+
#signedKeyOwner = null;
|
|
11659
|
+
#signatureChecks = /* @__PURE__ */ new WeakMap();
|
|
11660
|
+
#getSignatureCheck(documentLoader, contextLoader) {
|
|
11661
|
+
let checks = this.#signatureChecks.get(documentLoader);
|
|
11662
|
+
if (checks == null) {
|
|
11663
|
+
checks = /* @__PURE__ */ new WeakMap();
|
|
11664
|
+
this.#signatureChecks.set(documentLoader, checks);
|
|
11665
|
+
}
|
|
11666
|
+
let check = checks.get(contextLoader);
|
|
11667
|
+
if (check == null) {
|
|
11668
|
+
check = {};
|
|
11669
|
+
checks.set(contextLoader, check);
|
|
11670
|
+
}
|
|
11671
|
+
return check;
|
|
11672
|
+
}
|
|
11560
11673
|
async getSignedKey(options = {}) {
|
|
11561
11674
|
if (this.#signedKey != null) return this.#signedKey;
|
|
11562
|
-
|
|
11675
|
+
const documentLoader = options.documentLoader ?? this.documentLoader;
|
|
11676
|
+
const contextLoader = options.contextLoader ?? this.contextLoader;
|
|
11677
|
+
const check = this.#getSignatureCheck(documentLoader, contextLoader);
|
|
11678
|
+
if (check.key != null) return check.key;
|
|
11679
|
+
const promise = verifyRequest(this.request, {
|
|
11563
11680
|
...this,
|
|
11564
|
-
contextLoader
|
|
11565
|
-
documentLoader
|
|
11681
|
+
contextLoader,
|
|
11682
|
+
documentLoader,
|
|
11566
11683
|
timeWindow: this.federation.signatureTimeWindow,
|
|
11567
11684
|
maxSignatures: this.federation.maxHttpSignatures,
|
|
11568
11685
|
meterProvider: this.meterProvider,
|
|
11569
11686
|
tracerProvider: options.tracerProvider ?? this.tracerProvider
|
|
11687
|
+
}).then((key) => {
|
|
11688
|
+
if (key != null) this.#signedKey ??= key;
|
|
11689
|
+
return key;
|
|
11690
|
+
}).catch((error) => {
|
|
11691
|
+
if (check.key === promise) check.key = void 0;
|
|
11692
|
+
throw error;
|
|
11570
11693
|
});
|
|
11694
|
+
check.key = promise;
|
|
11695
|
+
return await promise;
|
|
11571
11696
|
}
|
|
11572
|
-
#signedKeyOwner = void 0;
|
|
11573
11697
|
async getSignedKeyOwner(options = {}) {
|
|
11574
11698
|
if (this.#signedKeyOwner != null) return this.#signedKeyOwner;
|
|
11575
|
-
const
|
|
11576
|
-
|
|
11577
|
-
|
|
11578
|
-
|
|
11579
|
-
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
|
|
11583
|
-
|
|
11584
|
-
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11699
|
+
const normalizedOptions = {
|
|
11700
|
+
documentLoader: options.documentLoader ?? this.documentLoader,
|
|
11701
|
+
contextLoader: options.contextLoader ?? this.contextLoader,
|
|
11702
|
+
tracerProvider: options.tracerProvider ?? this.tracerProvider
|
|
11703
|
+
};
|
|
11704
|
+
const check = this.#getSignatureCheck(normalizedOptions.documentLoader, normalizedOptions.contextLoader);
|
|
11705
|
+
if (check.owner != null) return check.owner;
|
|
11706
|
+
const promise = this.getSignedKey(normalizedOptions).then(async (key) => {
|
|
11707
|
+
if (key == null) {
|
|
11708
|
+
if (check.owner === promise) check.owner = void 0;
|
|
11709
|
+
return null;
|
|
11710
|
+
}
|
|
11711
|
+
try {
|
|
11712
|
+
const owner = await getKeyOwner(key, normalizedOptions);
|
|
11713
|
+
if (owner != null) this.#signedKeyOwner ??= owner;
|
|
11714
|
+
return owner;
|
|
11715
|
+
} catch (error) {
|
|
11716
|
+
if (error instanceof FetchError) {
|
|
11717
|
+
getLogger([
|
|
11718
|
+
"fedify",
|
|
11719
|
+
"federation",
|
|
11720
|
+
"actor"
|
|
11721
|
+
]).warn("Failed to fetch the key owner {keyOwner} of {keyId} while verifying the request signature; treating the request as unauthenticated: {error}", {
|
|
11722
|
+
keyId: key.id?.href,
|
|
11723
|
+
keyOwner: error.url.href,
|
|
11724
|
+
error
|
|
11725
|
+
});
|
|
11726
|
+
return null;
|
|
11727
|
+
}
|
|
11728
|
+
throw error;
|
|
11595
11729
|
}
|
|
11730
|
+
}).catch((error) => {
|
|
11731
|
+
if (check.owner === promise) check.owner = void 0;
|
|
11596
11732
|
throw error;
|
|
11597
|
-
}
|
|
11733
|
+
});
|
|
11734
|
+
check.owner = promise;
|
|
11735
|
+
return await promise;
|
|
11598
11736
|
}
|
|
11599
11737
|
async isSignedByAudience(object, options = {}) {
|
|
11600
11738
|
return await isInAudience(object, null) || await isInAudience(object, await this.getSignedKeyOwner(options), options);
|