@fedify/fedify 2.4.0-dev.2077 → 2.4.0-dev.2093
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-i3jxVe9m.mjs → builder-Ld4Y9ymR.mjs} +34 -3
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-Cayy-ECj.mjs → compound-proof-CTx_lewQ.mjs} +191 -13
- package/dist/{deno-Djkzd-G_.mjs → deno-8t8k_6Yv.mjs} +1 -1
- package/dist/{docloader-dA0RswUV.mjs → docloader-DJKcsAiF.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -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/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +254 -5
- package/dist/federation/portable-gateway-keys.test.mjs +72 -11
- package/dist/federation/portable-inbox.test.mjs +513 -6
- package/dist/federation/portable.test.mjs +182 -4
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +89 -1
- package/dist/{federation-CFd0Jhhv.cjs → federation-aSHO04ob.cjs} +472 -74
- package/dist/{http-CCJ97Jql.mjs → http-B2ftY0Ha.mjs} +4 -4
- package/dist/{http-Dvy_IwRa.cjs → http-DSaSAN7X.cjs} +1609 -1457
- package/dist/{http-aqh3yFdM.js → http-DyT7DP1Y.js} +1518 -1390
- package/dist/{key-IxTTC8Yf.mjs → key-Dwm4VswT.mjs} +39 -99
- package/dist/{kv-cache-BP69NV0P.cjs → kv-cache-BSJAlP4C.cjs} +1 -1
- package/dist/{kv-cache-Bwwbh-xR.mjs → kv-cache-D0yYukqk.mjs} +1 -1
- package/dist/{kv-cache-Y6SDRSX6.js → kv-cache-DmroAz3M.js} +1 -1
- package/dist/{ld-l0B9nREb.mjs → ld-BYZOoDzZ.mjs} +3 -3
- package/dist/{metrics-D1p6hv4z.mjs → metrics-B7YwKTFr.mjs} +1 -1
- package/dist/{middleware-BDjCUHMS.mjs → middleware-BNeqKSsL.mjs} +1 -1
- package/dist/{middleware-SSfryVzY.js → middleware-Blq0882d.js} +480 -82
- package/dist/{middleware-BMgzqBtr.mjs → middleware-E_0IH72m.mjs} +278 -80
- package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
- package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-DGbYmXqU.mjs → owner-Cgx2Dc7I.mjs} +5 -4
- package/dist/{owner-jH1z_YfD.js → owner-DAGSC5gX.js} +2 -2
- package/dist/{owner-BpOxL5PG.cjs → owner-szIZl4qI.cjs} +2 -2
- package/dist/{portable-key-CCwJGSsU.mjs → portable-key-Cn1WJSN1.mjs} +60 -41
- package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
- package/dist/{proof-CVL7JnZd.mjs → proof-DVXC6nYz.mjs} +59 -18
- package/dist/{send-Ejy1jOz9.mjs → send-D-hOvYKy.mjs} +3 -3
- 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 +160 -3
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +3 -3
- package/dist/sig/mod.d.cts +1 -1
- package/dist/sig/mod.d.ts +1 -1
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +69 -5
- package/dist/sig/portable-key.test.mjs +176 -10
- package/dist/sig/proof.test.mjs +107 -4
- package/dist/{sig-Dpy1B1Ad.js → sig-B5Ell7pO.js} +19 -3
- package/dist/{sig-xoqy3ATq.cjs → sig-CXOKN4Br.cjs} +19 -3
- package/dist/{temporal-DNJ1cNpr.mjs → temporal-CqpKGEPk.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
|
|
5
|
+
import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-B7YwKTFr.mjs";
|
|
6
6
|
import { t as formatAcceptSignature } from "./accept-CPVqki78.mjs";
|
|
7
|
-
import {
|
|
7
|
+
import { a as isCompatibleKeyId, c as isPortableUri, i as hasPortableActor, n as getGatewayKeyBase, r as getPortableDid, s as isPortableId, t as getCanonicalPortableId } from "./portable-key-id-Bva5iT3W.mjs";
|
|
8
|
+
import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-Dwm4VswT.mjs";
|
|
8
9
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
|
|
9
|
-
import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-
|
|
10
|
-
import {
|
|
11
|
-
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-
|
|
12
|
-
import { t as getAuthenticatedDocumentLoader } from "./docloader-
|
|
13
|
-
import { n as kvCache } from "./kv-cache-
|
|
10
|
+
import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-BYZOoDzZ.mjs";
|
|
11
|
+
import { i as signObject, l as verifyPortableObjectProofWithRoot, o as verifyObject, r as hasProofLike, s as verifyPortableObjectProof, t as InvalidPortableObjectIdError } from "./proof-DVXC6nYz.mjs";
|
|
12
|
+
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-B2ftY0Ha.mjs";
|
|
13
|
+
import { t as getAuthenticatedDocumentLoader } from "./docloader-DJKcsAiF.mjs";
|
|
14
|
+
import { n as kvCache } from "./kv-cache-D0yYukqk.mjs";
|
|
14
15
|
import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-DpnpgjyX.mjs";
|
|
15
16
|
import { t as nodeInfoToJson } from "./types-DE_LV1JB.mjs";
|
|
16
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
17
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-
|
|
18
|
-
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-
|
|
17
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-Cgx2Dc7I.mjs";
|
|
18
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-D-hOvYKy.mjs";
|
|
19
|
+
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-Ld4Y9ymR.mjs";
|
|
19
20
|
import { t as CircuitBreaker } from "./circuit-breaker-C5NqoLBJ.mjs";
|
|
20
21
|
import { t as buildCollectionSynchronizationHeader } from "./collection-CnnP1UAv.mjs";
|
|
21
|
-
import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "./compound-proof-
|
|
22
|
+
import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "./compound-proof-CTx_lewQ.mjs";
|
|
22
23
|
import { t as KvKeyCache } from "./keycache-BUDHB5rY.mjs";
|
|
23
24
|
import { t as acceptsJsonLd } from "./negotiation-CCocWT_N.mjs";
|
|
24
|
-
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-
|
|
25
|
+
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CqpKGEPk.mjs";
|
|
25
26
|
import { t as createExponentialBackoffPolicy } from "./retry-zX5xw5e0.mjs";
|
|
26
27
|
import { t as TaskCodec } from "./codec-C6rTrpIp.mjs";
|
|
27
28
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
@@ -30,8 +31,9 @@ import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey,
|
|
|
30
31
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
31
32
|
import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
32
33
|
import { uniq } from "es-toolkit";
|
|
33
|
-
import { FetchError, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin,
|
|
34
|
+
import { FetchError, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDigestMultibase, parseHashlink, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
|
|
34
35
|
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";
|
|
36
|
+
import { isCompatibleEf61Iri } from "@fedify/vocab-runtime/internal/portable-dereference";
|
|
35
37
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
36
38
|
import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
37
39
|
import { domainToASCII } from "node:url";
|
|
@@ -789,14 +791,7 @@ async function handlePortableObject(request, { values, context, objectDispatcher
|
|
|
789
791
|
} });
|
|
790
792
|
}
|
|
791
793
|
function isRequestedPortableObject(object, canonicalId) {
|
|
792
|
-
|
|
793
|
-
if (object.id.protocol !== "ap:" && object.id.protocol !== "ap+ef61:") return false;
|
|
794
|
-
try {
|
|
795
|
-
return canonicalizePortableUri(formatIri(object.id)) === canonicalId;
|
|
796
|
-
} catch (error) {
|
|
797
|
-
if (error instanceof TypeError) return false;
|
|
798
|
-
throw error;
|
|
799
|
-
}
|
|
794
|
+
return object.id != null && getCanonicalPortableId(object.id) === canonicalId;
|
|
800
795
|
}
|
|
801
796
|
async function getJsonLdRootId(jsonLd, contextLoader) {
|
|
802
797
|
const expanded = await jsonld.expand(jsonLd, {
|
|
@@ -808,6 +803,7 @@ async function getJsonLdRootId(jsonLd, contextLoader) {
|
|
|
808
803
|
return typeof id === "string" ? id : null;
|
|
809
804
|
}
|
|
810
805
|
function isRequestedPortableId(id, canonicalId) {
|
|
806
|
+
if (!/^ap(?:\+ef61)?:\/\//i.test(id)) return URL.canParse(id) && getCanonicalPortableId(new URL(id)) === canonicalId;
|
|
811
807
|
try {
|
|
812
808
|
return canonicalizePortableUri(id) === canonicalId;
|
|
813
809
|
} catch (error) {
|
|
@@ -858,13 +854,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
|
858
854
|
* @param parameters The parameters for handling the collection.
|
|
859
855
|
* @returns A promise that resolves to an HTTP response.
|
|
860
856
|
*/
|
|
861
|
-
async function handleCollection(request, { name: name$
|
|
862
|
-
const spanName = name$
|
|
857
|
+
async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
858
|
+
const spanName = name$2.trim().replace(/\s+/g, "_");
|
|
863
859
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
864
860
|
const tracer = tracerProvider.getTracer(name, version);
|
|
865
861
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
866
862
|
const metricBase = {
|
|
867
|
-
kind: getCollectionMetricKind(name$
|
|
863
|
+
kind: getCollectionMetricKind(name$2),
|
|
868
864
|
page: cursor != null,
|
|
869
865
|
dispatcher: "built_in"
|
|
870
866
|
};
|
|
@@ -905,7 +901,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
905
901
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
906
902
|
return await onNotFound(request);
|
|
907
903
|
}
|
|
908
|
-
const items = filterCollectionItems(page.items, name$
|
|
904
|
+
const items = filterCollectionItems(page.items, name$2, filterPredicate);
|
|
909
905
|
itemCount = items.length;
|
|
910
906
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
911
907
|
return items;
|
|
@@ -945,7 +941,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
945
941
|
} else {
|
|
946
942
|
const uri = new URL(baseUri);
|
|
947
943
|
uri.searchParams.set("cursor", cursor);
|
|
948
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
944
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
|
|
949
945
|
kind: SpanKind.SERVER,
|
|
950
946
|
attributes: {
|
|
951
947
|
"activitypub.collection.id": uri.href,
|
|
@@ -961,7 +957,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
961
957
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
962
958
|
return await onNotFound(request);
|
|
963
959
|
}
|
|
964
|
-
const items = filterCollectionItems(page.items, name$
|
|
960
|
+
const items = filterCollectionItems(page.items, name$2, filterPredicate);
|
|
965
961
|
itemCount = items.length;
|
|
966
962
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
967
963
|
return {
|
|
@@ -1670,7 +1666,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1670
1666
|
return await respondInvalidActivity(error);
|
|
1671
1667
|
}
|
|
1672
1668
|
activityVerified = true;
|
|
1673
|
-
if (!skipSignatureVerification &&
|
|
1669
|
+
if (!skipSignatureVerification && isPortableActivity(activity)) try {
|
|
1674
1670
|
proofVerified = await verifyObject(Activity, jsonWithoutSig, {
|
|
1675
1671
|
contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
|
|
1676
1672
|
documentLoader: ctx.documentLoader,
|
|
@@ -1854,7 +1850,7 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1854
1850
|
"http_signatures.verified": httpSigKey != null,
|
|
1855
1851
|
"http_signatures.key_id": httpSigKey?.id?.href ?? ""
|
|
1856
1852
|
});
|
|
1857
|
-
if (!skipSignatureVerification && !proofVerified && (
|
|
1853
|
+
if (!skipSignatureVerification && !proofVerified && (isPortableActivity(activity) || isPortableGatewayKey(httpSigKey))) {
|
|
1858
1854
|
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
1859
1855
|
logger.error("The activity {activityId} of the portable actor {actorId} is not authenticated by a valid Object Integrity Proof.", {
|
|
1860
1856
|
activity: json,
|
|
@@ -1872,6 +1868,32 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1872
1868
|
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
1873
1869
|
});
|
|
1874
1870
|
}
|
|
1871
|
+
if (!skipSignatureVerification && activity.id != null && isPortableId(activity.id)) {
|
|
1872
|
+
const rejection = await verifyPortableActivityId(activity.id, jsonWithoutSig, {
|
|
1873
|
+
contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
|
|
1874
|
+
documentLoader: ctx.documentLoader,
|
|
1875
|
+
keyCache,
|
|
1876
|
+
meterProvider,
|
|
1877
|
+
tracerProvider
|
|
1878
|
+
});
|
|
1879
|
+
if (rejection != null) {
|
|
1880
|
+
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
1881
|
+
logger.error("The portable activity {activityId} is not authenticated by an Object Integrity Proof of its own DID: {reason}", {
|
|
1882
|
+
activity: json,
|
|
1883
|
+
recipient,
|
|
1884
|
+
activityId: activity.id.href,
|
|
1885
|
+
reason: rejection
|
|
1886
|
+
});
|
|
1887
|
+
span.setStatus({
|
|
1888
|
+
code: SpanStatusCode.ERROR,
|
|
1889
|
+
message: `The portable activity (${activity.id.href}) is not authenticated by an Object Integrity Proof of its own DID.`
|
|
1890
|
+
});
|
|
1891
|
+
return new Response("Portable activities must have valid Object Integrity Proofs made by their own DIDs.", {
|
|
1892
|
+
status: 401,
|
|
1893
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1875
1897
|
if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
|
|
1876
1898
|
if (deferredLdSignatureError != null) throw deferredLdSignatureError;
|
|
1877
1899
|
getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("actorKeyMismatch", httpSigKey.id == null ? void 0 : getRemoteHost(httpSigKey.id));
|
|
@@ -2109,8 +2131,8 @@ var CustomCollectionHandler = class {
|
|
|
2109
2131
|
* @param CollectionPage The CollectionPage constructor.
|
|
2110
2132
|
* @param filterPredicate Optional filter predicate for items.
|
|
2111
2133
|
*/
|
|
2112
|
-
constructor(name$
|
|
2113
|
-
this.name = name$
|
|
2134
|
+
constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
|
|
2135
|
+
this.name = name$1;
|
|
2114
2136
|
this.values = values;
|
|
2115
2137
|
this.context = context;
|
|
2116
2138
|
this.callbacks = callbacks;
|
|
@@ -2601,7 +2623,41 @@ const MIN_COMPONENTS = [
|
|
|
2601
2623
|
* establish.
|
|
2602
2624
|
*/
|
|
2603
2625
|
function isPortableGatewayKey(key) {
|
|
2604
|
-
return key?.id != null && isCompatibleKeyId(key.id) && key.ownerId != null &&
|
|
2626
|
+
return key?.id != null && isCompatibleKeyId(key.id) && key.ownerId != null && isPortableId(key.ownerId);
|
|
2627
|
+
}
|
|
2628
|
+
/**
|
|
2629
|
+
* Checks whether an activity needs an Object Integrity Proof by its DID:
|
|
2630
|
+
* either it is performed by an [FEP-ef61] portable actor, or its own ID is
|
|
2631
|
+
* portable. Both `ap:` URIs and compatible identifiers count as portable.
|
|
2632
|
+
*
|
|
2633
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
2634
|
+
*/
|
|
2635
|
+
function isPortableActivity(activity) {
|
|
2636
|
+
return hasPortableActor(activity) || activity.id != null && isPortableId(activity.id);
|
|
2637
|
+
}
|
|
2638
|
+
/**
|
|
2639
|
+
* Verifies that a portable activity's own ID belongs to the DID that signed
|
|
2640
|
+
* it, on the JSON-LD document received. The ID the proof policy checks has
|
|
2641
|
+
* to be the parsed activity's ID, so that a remote context that changes
|
|
2642
|
+
* between loads cannot make the two name different objects.
|
|
2643
|
+
* @returns `null` if the ID is verified, or the reason why it is not.
|
|
2644
|
+
*/
|
|
2645
|
+
async function verifyPortableActivityId(activityId, jsonLd, options) {
|
|
2646
|
+
const expectedId = getCanonicalPortableId(activityId);
|
|
2647
|
+
if (expectedId == null) return "malformed portable ID";
|
|
2648
|
+
let verification;
|
|
2649
|
+
try {
|
|
2650
|
+
verification = await verifyPortableObjectProofWithRoot(jsonLd, options);
|
|
2651
|
+
} catch (error) {
|
|
2652
|
+
if (error instanceof InvalidPortableObjectIdError) return "malformed portable ID";
|
|
2653
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
2654
|
+
return "malformed document";
|
|
2655
|
+
}
|
|
2656
|
+
const { result, root } = verification;
|
|
2657
|
+
if (!result.verified) return result.reason.type;
|
|
2658
|
+
const rootId = root?.["@id"];
|
|
2659
|
+
if (typeof rootId !== "string" || getCanonicalPortableId(parseIri(rootId)) !== expectedId) return "the verified document has another ID";
|
|
2660
|
+
return null;
|
|
2605
2661
|
}
|
|
2606
2662
|
//#endregion
|
|
2607
2663
|
//#region src/federation/portable-inbox.ts
|
|
@@ -2649,16 +2705,17 @@ function resolvePortableInboxRecipient(actor, { authority, canonicalInboxId, loc
|
|
|
2649
2705
|
reason: "notFound"
|
|
2650
2706
|
};
|
|
2651
2707
|
const id = actor.id;
|
|
2652
|
-
|
|
2708
|
+
const did = id == null || !isPortableId(id) ? null : getPortableDid(id);
|
|
2709
|
+
if (id == null || did == null) return {
|
|
2653
2710
|
status: "rejected",
|
|
2654
2711
|
reason: "notPortable"
|
|
2655
2712
|
};
|
|
2656
|
-
if (
|
|
2713
|
+
if (did !== authority) return {
|
|
2657
2714
|
status: "rejected",
|
|
2658
2715
|
reason: "authorityMismatch"
|
|
2659
2716
|
};
|
|
2660
|
-
const inboxId = actor.inboxId;
|
|
2661
|
-
if (inboxId == null ||
|
|
2717
|
+
const inboxId = actor.inboxId == null ? null : toPortableUri(actor.inboxId);
|
|
2718
|
+
if (inboxId == null || getCanonicalPortableId(inboxId) !== canonicalInboxId) return {
|
|
2662
2719
|
status: "rejected",
|
|
2663
2720
|
reason: "inboxMismatch"
|
|
2664
2721
|
};
|
|
@@ -2670,23 +2727,22 @@ function resolvePortableInboxRecipient(actor, { authority, canonicalInboxId, loc
|
|
|
2670
2727
|
return {
|
|
2671
2728
|
status: "accepted",
|
|
2672
2729
|
recipient: {
|
|
2730
|
+
actorId: id,
|
|
2673
2731
|
inboxId,
|
|
2674
2732
|
canonicalInboxId,
|
|
2675
2733
|
gateways
|
|
2676
2734
|
}
|
|
2677
2735
|
};
|
|
2678
2736
|
}
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
}
|
|
2687
|
-
function canonicalize(iri) {
|
|
2737
|
+
/**
|
|
2738
|
+
* Gets the portable form of an `ap:` or `ap+ef61:` URI or a compatible
|
|
2739
|
+
* identifier, keeping its query.
|
|
2740
|
+
* @returns The portable URI, or `null` if the ID is neither, or is malformed.
|
|
2741
|
+
*/
|
|
2742
|
+
function toPortableUri(id) {
|
|
2743
|
+
if (isPortableUri(id)) return id;
|
|
2688
2744
|
try {
|
|
2689
|
-
return
|
|
2745
|
+
return fromCompatibleEf61Id(id);
|
|
2690
2746
|
} catch (error) {
|
|
2691
2747
|
if (error instanceof TypeError) return null;
|
|
2692
2748
|
throw error;
|
|
@@ -2703,8 +2759,11 @@ function canonicalize(iri) {
|
|
|
2703
2759
|
* Forwarding is therefore best effort. Without {@link KvStore.cas}, nothing is
|
|
2704
2760
|
* forwarded.
|
|
2705
2761
|
*
|
|
2706
|
-
* Forwarded requests are
|
|
2707
|
-
*
|
|
2762
|
+
* Forwarded requests are signed with HTTP Signatures by the RSA key among
|
|
2763
|
+
* {@link ForwardPortableInboxActivityParameters.getKeys}, if any, so that
|
|
2764
|
+
* servers requiring HTTP Signatures accept them, and are sent unsigned
|
|
2765
|
+
* otherwise. Either way, the receiving gateways authenticate the activity by
|
|
2766
|
+
* its own proof.
|
|
2708
2767
|
* @param parameters The parameters.
|
|
2709
2768
|
* @returns The target inbox URLs that the activity was handed off to.
|
|
2710
2769
|
*/
|
|
@@ -2717,7 +2776,7 @@ async function forwardPortableInboxActivity(parameters) {
|
|
|
2717
2776
|
const { recipient, activityId, excludedOrigins, kv, kvPrefix, outboxQueue } = parameters;
|
|
2718
2777
|
const { maxTargets, ttl } = parameters.options ?? resolvePortableInboxForwardingOptions();
|
|
2719
2778
|
if (kv.cas == null || maxTargets < 1) return [];
|
|
2720
|
-
const canonicalActivityId =
|
|
2779
|
+
const canonicalActivityId = getCanonicalPortableId(activityId) ?? activityId.href;
|
|
2721
2780
|
const targets = getForwardingTargets(recipient, excludedOrigins);
|
|
2722
2781
|
if (targets.length > maxTargets) {
|
|
2723
2782
|
logger.warn("The portable actor that owns the inbox {inbox} has more than {max} other gateways; the activity {activityId} is forwarded only to the first {max} of them.", {
|
|
@@ -2776,20 +2835,59 @@ function getForwardingTargets(recipient, excludedOrigins) {
|
|
|
2776
2835
|
}
|
|
2777
2836
|
return targets;
|
|
2778
2837
|
}
|
|
2779
|
-
|
|
2838
|
+
/**
|
|
2839
|
+
* Resolves the key that signs forwarded requests: the first RSASSA-PKCS1-v1_5
|
|
2840
|
+
* key, as {@link sendActivity} signs requests only with such a key. Since
|
|
2841
|
+
* the targets have already been claimed, the activity is forwarded unsigned
|
|
2842
|
+
* rather than not at all if the key cannot be resolved or used.
|
|
2843
|
+
*/
|
|
2844
|
+
async function resolveForwardingKey({ getKeys, activityId }) {
|
|
2845
|
+
if (getKeys == null) return null;
|
|
2846
|
+
const logger = getLogger([
|
|
2847
|
+
"fedify",
|
|
2848
|
+
"federation",
|
|
2849
|
+
"inbox"
|
|
2850
|
+
]);
|
|
2851
|
+
let key;
|
|
2852
|
+
try {
|
|
2853
|
+
key = (await getKeys()).find((k) => k.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5");
|
|
2854
|
+
} catch (error) {
|
|
2855
|
+
logger.error("Failed to get the gateway keys to sign forwarded requests with; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
|
|
2856
|
+
activityId: activityId.href,
|
|
2857
|
+
error
|
|
2858
|
+
});
|
|
2859
|
+
return null;
|
|
2860
|
+
}
|
|
2861
|
+
if (key == null) return null;
|
|
2862
|
+
try {
|
|
2863
|
+
validateCryptoKey(key.privateKey, "private");
|
|
2864
|
+
} catch (error) {
|
|
2865
|
+
if (!(error instanceof TypeError)) throw error;
|
|
2866
|
+
logger.warn("The gateway key {keyId} cannot sign forwarded requests; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
|
|
2867
|
+
keyId: key.keyId.href,
|
|
2868
|
+
activityId: activityId.href,
|
|
2869
|
+
error
|
|
2870
|
+
});
|
|
2871
|
+
return null;
|
|
2872
|
+
}
|
|
2873
|
+
return key;
|
|
2874
|
+
}
|
|
2875
|
+
async function forwardImmediately(parameters, inboxes) {
|
|
2876
|
+
const { activity, activityId, activityType, allowPrivateAddress, specDeterminer, options, meterProvider, tracerProvider } = parameters;
|
|
2780
2877
|
const logger = getLogger([
|
|
2781
2878
|
"fedify",
|
|
2782
2879
|
"federation",
|
|
2783
2880
|
"inbox"
|
|
2784
2881
|
]);
|
|
2785
2882
|
const deadline = (options ?? resolvePortableInboxForwardingOptions()).deadline.total("millisecond");
|
|
2786
|
-
const
|
|
2883
|
+
const forwarding = resolveForwardingKey(parameters).then((key) => Promise.all(inboxes.map((inbox) => sendActivity({
|
|
2787
2884
|
activity,
|
|
2788
2885
|
activityId: activityId.href,
|
|
2789
2886
|
activityType,
|
|
2790
|
-
keys: [],
|
|
2887
|
+
keys: key == null ? [] : [key],
|
|
2791
2888
|
inbox,
|
|
2792
2889
|
allowPrivateAddress,
|
|
2890
|
+
specDeterminer,
|
|
2793
2891
|
meterProvider,
|
|
2794
2892
|
tracerProvider
|
|
2795
2893
|
}).catch((error) => {
|
|
@@ -2798,9 +2896,9 @@ async function forwardImmediately({ activity, activityId, activityType, allowPri
|
|
|
2798
2896
|
inbox: inbox.href,
|
|
2799
2897
|
error
|
|
2800
2898
|
});
|
|
2801
|
-
}));
|
|
2899
|
+
}))));
|
|
2802
2900
|
let timer;
|
|
2803
|
-
const timedOut = await Promise.race([
|
|
2901
|
+
const timedOut = await Promise.race([forwarding.then(() => false), new Promise((resolve) => {
|
|
2804
2902
|
timer = setTimeout(() => resolve(true), deadline);
|
|
2805
2903
|
})]);
|
|
2806
2904
|
clearTimeout(timer);
|
|
@@ -2809,12 +2907,27 @@ async function forwardImmediately({ activity, activityId, activityType, allowPri
|
|
|
2809
2907
|
deadline
|
|
2810
2908
|
});
|
|
2811
2909
|
}
|
|
2812
|
-
async function enqueueForwarding(
|
|
2910
|
+
async function enqueueForwarding(parameters, outboxQueue, inboxes) {
|
|
2911
|
+
const { activity, activityId, activityType, baseUrl, startQueue, meterProvider } = parameters;
|
|
2813
2912
|
const logger = getLogger([
|
|
2814
2913
|
"fedify",
|
|
2815
2914
|
"federation",
|
|
2816
2915
|
"inbox"
|
|
2817
2916
|
]);
|
|
2917
|
+
const key = await resolveForwardingKey(parameters);
|
|
2918
|
+
const keys = [];
|
|
2919
|
+
if (key != null) try {
|
|
2920
|
+
keys.push({
|
|
2921
|
+
keyId: key.keyId.href,
|
|
2922
|
+
privateKey: await exportJwk(key.privateKey)
|
|
2923
|
+
});
|
|
2924
|
+
} catch (error) {
|
|
2925
|
+
logger.error("Failed to export the gateway key {keyId}; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
|
|
2926
|
+
keyId: key.keyId.href,
|
|
2927
|
+
activityId: activityId.href,
|
|
2928
|
+
error
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2818
2931
|
startQueue?.();
|
|
2819
2932
|
const started = (/* @__PURE__ */ new Date()).toISOString();
|
|
2820
2933
|
const traceContext = {};
|
|
@@ -2824,7 +2937,7 @@ async function enqueueForwarding({ activity, activityId, activityType, baseUrl,
|
|
|
2824
2937
|
type: "outbox",
|
|
2825
2938
|
id: crypto.randomUUID(),
|
|
2826
2939
|
baseUrl,
|
|
2827
|
-
keys
|
|
2940
|
+
keys,
|
|
2828
2941
|
activity,
|
|
2829
2942
|
activityId: activityId.href,
|
|
2830
2943
|
activityType,
|
|
@@ -2850,34 +2963,50 @@ async function enqueueForwarding({ activity, activityId, activityType, baseUrl,
|
|
|
2850
2963
|
}
|
|
2851
2964
|
//#endregion
|
|
2852
2965
|
//#region src/federation/outgoing-proof.ts
|
|
2853
|
-
function isPortableUrl(url) {
|
|
2854
|
-
return url.protocol === "ap:" || url.protocol === "ap+ef61:";
|
|
2855
|
-
}
|
|
2856
2966
|
function formatKeyIds(candidates) {
|
|
2857
2967
|
return candidates.map((c) => c.verificationMethod.href).join(", ");
|
|
2858
2968
|
}
|
|
2859
2969
|
/**
|
|
2860
2970
|
* Rejects an activity of a portable actor whose ID cannot be authenticated by
|
|
2861
2971
|
* the actor's DID: FEP-ef61 requires the activity to be a portable object
|
|
2862
|
-
* signed by that DID, so its ID has to be a portable ID
|
|
2972
|
+
* signed by that DID, so its ID has to be a portable ID, i.e., an `ap:` or
|
|
2973
|
+
* `ap+ef61:` URI or a compatible identifier, with the same DID. A portable
|
|
2974
|
+
* actor is one whose ID is such an ID as well.
|
|
2863
2975
|
* @throws {TypeError} If the activity is performed by a portable actor, but
|
|
2864
|
-
* its ID is not a portable ID of the actor's DID,
|
|
2865
|
-
* portable actors do not share a DID
|
|
2976
|
+
* its ID is not a portable ID of the actor's DID, its
|
|
2977
|
+
* portable actors do not share a DID, or the ID of one of
|
|
2978
|
+
* them is a malformed compatible identifier.
|
|
2866
2979
|
* @internal
|
|
2867
2980
|
*/
|
|
2868
2981
|
function assertPortableActorActivity(activity) {
|
|
2869
|
-
const portableActors = activity.actorIds.filter(
|
|
2982
|
+
const portableActors = activity.actorIds.filter((id) => isPortableId(id));
|
|
2870
2983
|
if (portableActors.length < 1) return;
|
|
2871
|
-
const
|
|
2872
|
-
|
|
2873
|
-
|
|
2984
|
+
const dids = portableActors.map((actorId) => {
|
|
2985
|
+
const did = getPortableDid(actorId);
|
|
2986
|
+
if (did == null) throw new TypeError(`The activity ${activity.id?.href} is performed by the actor ${actorId.href}, whose ID is a malformed FEP-ef61 portable ID. A compatible identifier must have a valid DID and an object path, and must not have credentials or @gateway location hints.`);
|
|
2987
|
+
return did;
|
|
2988
|
+
});
|
|
2989
|
+
const did = dids[0];
|
|
2990
|
+
if (dids.some((d) => d !== did)) throw new TypeError(`The activity ${activity.id?.href} has portable actors with different DIDs (${portableActors.map((a) => a.href).join(", ")}); a portable activity is signed by a single DID.`);
|
|
2991
|
+
if (activity.id == null || !isPortableId(activity.id) || getPortableDid(activity.id) !== did) throw new TypeError(`The activity ${activity.id?.href} is performed by the portable actor ${portableActors[0].href}, so its ID has to be an ap: or ap+ef61: URI, or an FEP-ef61 compatible identifier, with the same DID (${did}), as FEP-ef61 requires it to carry an Object Integrity Proof made by that DID.`);
|
|
2992
|
+
if (!isCompatibleEf61Iri(activity.id)) return;
|
|
2993
|
+
const mismatched = portableActors.find((actorId) => isCompatibleEf61Iri(actorId) && actorId.origin !== activity.id?.origin);
|
|
2994
|
+
if (mismatched == null) return;
|
|
2995
|
+
getLogger([
|
|
2996
|
+
"fedify",
|
|
2997
|
+
"federation",
|
|
2998
|
+
"outbox"
|
|
2999
|
+
]).warn("The activity {activityId} and its actor {actorId} are identified by FEP-ef61 compatible identifiers on different gateways. FEP-ef61 requires publishers to use the first gateway in the actor's gateways when constructing compatible identifiers.", {
|
|
3000
|
+
activityId: activity.id.href,
|
|
3001
|
+
actorId: mismatched.href
|
|
3002
|
+
});
|
|
2874
3003
|
}
|
|
2875
3004
|
/**
|
|
2876
3005
|
* Creates the Object Integrity Proofs Fedify attaches to an outgoing activity.
|
|
2877
3006
|
*
|
|
2878
3007
|
* Keys whose IDs are FEP-ef61 compatible identifiers are gateway keys, which
|
|
2879
3008
|
* never sign proofs. A portable activity, i.e., one with an `ap:` or
|
|
2880
|
-
* `ap+ef61:` ID, that carries no proof is signed only by the one Ed25519 key
|
|
3009
|
+
* `ap+ef61:` ID or a compatible identifier, that carries no proof is signed only by the one Ed25519 key
|
|
2881
3010
|
* whose verification method is a DID URL for its DID, even if it is the only
|
|
2882
3011
|
* key; if no key or more than one key qualifies, this function throws.
|
|
2883
3012
|
*
|
|
@@ -2916,7 +3045,7 @@ async function signOutgoingActivity(activity, candidates, options) {
|
|
|
2916
3045
|
break;
|
|
2917
3046
|
}
|
|
2918
3047
|
const keys = candidates.filter((c) => c.privateKey.algorithm.name === "Ed25519" && !isCompatibleKeyId(c.verificationMethod));
|
|
2919
|
-
if (!hasProof && activity.id != null &&
|
|
3048
|
+
if (!hasProof && activity.id != null && isPortableId(activity.id)) return await sign(selectPortableActivityKey(activity, activity.id, keys));
|
|
2920
3049
|
if (keys.length < 1 || hasProof && !options.appendToExistingProofs) return {
|
|
2921
3050
|
activity,
|
|
2922
3051
|
hasProof,
|
|
@@ -2949,8 +3078,9 @@ async function signOutgoingActivity(activity, candidates, options) {
|
|
|
2949
3078
|
* @throws {TypeError} If no key or more than one key qualifies.
|
|
2950
3079
|
*/
|
|
2951
3080
|
function selectPortableActivityKey(activity, activityId, keys) {
|
|
2952
|
-
const activityDid =
|
|
2953
|
-
|
|
3081
|
+
const activityDid = getPortableDid(activityId);
|
|
3082
|
+
if (activityDid == null) throw new TypeError(`Cannot sign the portable activity ${activityId.href}, as its ID is a malformed FEP-ef61 portable ID. A compatible identifier must have a valid DID and an object path, and must not have credentials or @gateway location hints.`);
|
|
3083
|
+
const eligible = keys.filter((c) => c.verificationMethod.protocol === "did:" && getPortableDid(c.verificationMethod) === activityDid);
|
|
2954
3084
|
if (eligible.length !== 1) throw new TypeError(`Cannot choose an Object Integrity Proof key for the portable activity ${activityId.href}: ` + (eligible.length < 1 ? `none of its ${keys.length} Ed25519 keys (${formatKeyIds(keys)}) ` : `${eligible.length} of its Ed25519 keys (${formatKeyIds(eligible)}) `) + `${eligible.length < 1 ? "is" : "are"} a DID URL verification method for ${activityDid}. FEP-ef61 requires a portable activity's proof to be made by its DID, and the map-local compound-proof profile accepts exactly one direct proof per map. Gateway keys, whose IDs are compatible identifiers, never sign proofs. Supply exactly one Ed25519 key whose key ID is a DID URL for ${activityDid}, or sign the activity with signObject() before sending it.`);
|
|
2955
3085
|
getLogger([
|
|
2956
3086
|
"fedify",
|
|
@@ -3197,6 +3327,27 @@ function getWebFingerSubjectAndAliases({ resourceUrl, actorUri, preferredUsernam
|
|
|
3197
3327
|
};
|
|
3198
3328
|
}
|
|
3199
3329
|
/**
|
|
3330
|
+
* Gets the compatible identifier that a WebFinger response links to for
|
|
3331
|
+
* an FEP-ef61 portable actor. An actor identified by an `ap:` or `ap+ef61:`
|
|
3332
|
+
* URI is linked through its compatible identifier on the given gateway, while
|
|
3333
|
+
* an actor identified by a compatible identifier is linked through its ID as
|
|
3334
|
+
* is, so that software without FEP-ef61 support finds the same ID in the actor
|
|
3335
|
+
* document.
|
|
3336
|
+
* @param actorId The portable actor's ID.
|
|
3337
|
+
* @param gateway The actor's first gateway.
|
|
3338
|
+
* @returns The compatible identifier, or `null` if the actor ID is malformed
|
|
3339
|
+
* or cannot be represented as a compatible identifier.
|
|
3340
|
+
*/
|
|
3341
|
+
function getCompatibleActorId(actorId, gateway) {
|
|
3342
|
+
try {
|
|
3343
|
+
if (isPortableUri(actorId)) return toCompatibleEf61Id(actorId, gateway);
|
|
3344
|
+
return fromCompatibleEf61Id(actorId) == null ? null : actorId;
|
|
3345
|
+
} catch (error) {
|
|
3346
|
+
if (error instanceof TypeError) return null;
|
|
3347
|
+
throw error;
|
|
3348
|
+
}
|
|
3349
|
+
}
|
|
3350
|
+
/**
|
|
3200
3351
|
* Computes the subject and aliases for an FEP-ef61 portable actor, whose
|
|
3201
3352
|
* WebFinger address takes its domain from the first gateway of the actor
|
|
3202
3353
|
* rather than from the actor ID. The canonical `ap:` ID is not emitted,
|
|
@@ -3360,21 +3511,23 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
3360
3511
|
});
|
|
3361
3512
|
let actorUri = context.getActorUri(identifier);
|
|
3362
3513
|
let portable;
|
|
3363
|
-
if (actor.id != null && (actor.id
|
|
3514
|
+
if (actor.id != null && isPortableId(actor.id)) {
|
|
3364
3515
|
const gateway = actor.gateway;
|
|
3365
|
-
|
|
3366
|
-
if (gateway != null && isGatewayUrl(gateway)) try {
|
|
3367
|
-
compatibleId = toCompatibleEf61Id(actor.id, gateway);
|
|
3368
|
-
} catch (error) {
|
|
3369
|
-
if (!(error instanceof TypeError)) throw error;
|
|
3370
|
-
}
|
|
3371
|
-
if (gateway == null || compatibleId == null) {
|
|
3516
|
+
if (gateway == null || !isGatewayUrl(gateway)) {
|
|
3372
3517
|
logger.error("The portable actor {actorId} (identifier: {identifier}) needs an HTTP(S) origin as the first item of its gateways property to be discovered through WebFinger.", {
|
|
3373
3518
|
actorId: formatIri(actor.id),
|
|
3374
3519
|
identifier
|
|
3375
3520
|
});
|
|
3376
3521
|
return await onNotFound(request);
|
|
3377
3522
|
}
|
|
3523
|
+
const compatibleId = getCompatibleActorId(actor.id, gateway);
|
|
3524
|
+
if (compatibleId == null) {
|
|
3525
|
+
logger.error("The portable actor {actorId} (identifier: {identifier}) cannot be discovered through WebFinger, as its ID is malformed or cannot be represented as an FEP-ef61 compatible identifier.", {
|
|
3526
|
+
actorId: formatIri(actor.id),
|
|
3527
|
+
identifier
|
|
3528
|
+
});
|
|
3529
|
+
return await onNotFound(request);
|
|
3530
|
+
}
|
|
3378
3531
|
actorUri = compatibleId;
|
|
3379
3532
|
portable = {
|
|
3380
3533
|
compatibleId,
|
|
@@ -5325,6 +5478,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5325
5478
|
outboxQueue: this.outboxQueue,
|
|
5326
5479
|
startQueue: this.manuallyStartQueue ? void 0 : () => this._startQueueInternal(contextData),
|
|
5327
5480
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
5481
|
+
getKeys: () => this.#getPortableGatewayKeyPairs(context, identifier, recipient),
|
|
5482
|
+
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
|
|
5328
5483
|
options: this.portableInboxForwarding,
|
|
5329
5484
|
meterProvider: this.meterProvider,
|
|
5330
5485
|
tracerProvider: this.tracerProvider
|
|
@@ -5333,6 +5488,49 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5333
5488
|
}
|
|
5334
5489
|
});
|
|
5335
5490
|
}
|
|
5491
|
+
/**
|
|
5492
|
+
* Gets this server's gateway key pairs for the portable actor that owns
|
|
5493
|
+
* a portable inbox, which sign the activities forwarded from the inbox.
|
|
5494
|
+
* @param context The context.
|
|
5495
|
+
* @param identifier The identifier of the portable actor.
|
|
5496
|
+
* @param recipient The portable inbox.
|
|
5497
|
+
* @returns The key pairs, or an empty array if the actor has no gateway
|
|
5498
|
+
* keys on this server.
|
|
5499
|
+
*/
|
|
5500
|
+
async #getPortableGatewayKeyPairs(context, identifier, recipient) {
|
|
5501
|
+
const logger = getLogger([
|
|
5502
|
+
"fedify",
|
|
5503
|
+
"federation",
|
|
5504
|
+
"inbox"
|
|
5505
|
+
]);
|
|
5506
|
+
if (this.actorCallbacks?.keyPairsDispatcher == null || this.actorCallbacks.portableActorIdMapper == null) {
|
|
5507
|
+
logger.debug("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as gateway keys need both a key pairs dispatcher and a portable actor ID mapper.", { inbox: recipient.canonicalInboxId });
|
|
5508
|
+
return [];
|
|
5509
|
+
}
|
|
5510
|
+
const actorId = getCanonicalPortableId(recipient.actorId);
|
|
5511
|
+
if (actorId == null) return [];
|
|
5512
|
+
const keyPairs = await context.getActorKeyPairs(identifier);
|
|
5513
|
+
for (const { cryptographicKey } of keyPairs) {
|
|
5514
|
+
const owner = cryptographicKey.ownerId;
|
|
5515
|
+
const canonicalOwner = owner == null ? null : getCanonicalPortableId(owner);
|
|
5516
|
+
if (canonicalOwner === actorId) continue;
|
|
5517
|
+
if (canonicalOwner == null) logger.debug("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as the key pairs of the actor {identifier} are not the gateway keys of a portable actor.", {
|
|
5518
|
+
inbox: recipient.canonicalInboxId,
|
|
5519
|
+
identifier
|
|
5520
|
+
});
|
|
5521
|
+
else logger.warn("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as the key pairs of the actor {identifier} are the gateway keys of {owner}, not of the recipient {actorId}.", {
|
|
5522
|
+
inbox: recipient.canonicalInboxId,
|
|
5523
|
+
identifier,
|
|
5524
|
+
owner: canonicalOwner,
|
|
5525
|
+
actorId
|
|
5526
|
+
});
|
|
5527
|
+
return [];
|
|
5528
|
+
}
|
|
5529
|
+
return keyPairs.map(({ keyId, privateKey }) => ({
|
|
5530
|
+
keyId,
|
|
5531
|
+
privateKey
|
|
5532
|
+
}));
|
|
5533
|
+
}
|
|
5336
5534
|
async #fetchHashlinkMedia(request, media, { onNotFound, contextData, span, metricState }) {
|
|
5337
5535
|
metricState.endpoint = "hashlink_media";
|
|
5338
5536
|
metricState.routeTemplate = HASHLINK_MEDIA_ROUTE_TEMPLATE;
|
|
@@ -6564,4 +6762,4 @@ function getRequestId(request) {
|
|
|
6564
6762
|
return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
6565
6763
|
}
|
|
6566
6764
|
//#endregion
|
|
6567
|
-
export {
|
|
6765
|
+
export { handleNodeInfoJrd as C, handleNodeInfo as S, autoIdAssigner as T, respondWithObject as _, OutboxContextImpl as a, parseHashlinkGatewayRequest as b, assertPortableActorActivity as c, handleActor as d, handleCollection as f, handleOutbox as g, handleObject as h, KvSpecDeterminer as i, forwardPortableInboxActivity as l, handleInbox as m, FederationImpl as n, createFederation as o, handleCustomCollection as p, InboxContextImpl as r, handleWebFinger as s, ContextImpl as t, resolvePortableInboxForwardingOptions as u, respondWithObjectIfAcceptable as v, actorDehydrator as w, handleBenchmarkTrigger as x, PORTABLE_OBJECT_CONTENT_TYPE as y };
|