@fedify/fedify 2.4.0-dev.2093 → 2.4.0-dev.2103
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-Ld4Y9ymR.mjs → builder-CHht75pX.mjs} +2 -2
- 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-CTx_lewQ.mjs → compound-proof-DKKH_Nr1.mjs} +2 -2
- package/dist/{context-BSywYspi.d.ts → context-CfJE_oQu.d.ts} +78 -13
- package/dist/{context-DlZhxdFT.mjs → context-CoNRxGYp.mjs} +2 -1
- package/dist/{context-CZjZYXFC.d.cts → context-mDTxTw-S.d.cts} +78 -13
- package/dist/{deno-8t8k_6Yv.mjs → deno-CkjiYK_Z.mjs} +1 -1
- package/dist/{docloader-DJKcsAiF.mjs → docloader-C1hAJnWc.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 +4 -4
- package/dist/federation/handler.test.mjs +6 -6
- 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 +8 -8
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable-actor.test.d.mts +2 -0
- package/dist/federation/portable-actor.test.mjs +446 -0
- package/dist/federation/portable-gateway-keys.test.mjs +4 -4
- package/dist/federation/portable-inbox.test.mjs +396 -10
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +2 -2
- package/dist/federation/tasks/tasks.test.mjs +3 -3
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{federation-aSHO04ob.cjs → federation-C1dL6SiT.cjs} +1267 -1086
- package/dist/{http-B2ftY0Ha.mjs → http-BU-8fUrz.mjs} +82 -7
- package/dist/{http-DSaSAN7X.cjs → http-Cala2Grs.cjs} +89 -2
- package/dist/{http-DyT7DP1Y.js → http-tTlTeFa0.js} +78 -3
- package/dist/{key-Dwm4VswT.mjs → key-I8gLBkcf.mjs} +2 -2
- package/dist/{kv-cache-BSJAlP4C.cjs → kv-cache-Bl5EBFrU.cjs} +1 -1
- package/dist/{kv-cache-DmroAz3M.js → kv-cache-CxQu_cd4.js} +1 -1
- package/dist/{kv-cache-D0yYukqk.mjs → kv-cache-DJFoGRWQ.mjs} +1 -1
- package/dist/{ld-BYZOoDzZ.mjs → ld-wiJbqJHa.mjs} +3 -3
- package/dist/{metrics-B7YwKTFr.mjs → metrics-BCAa5-kE.mjs} +1 -1
- package/dist/{middleware-BNeqKSsL.mjs → middleware-6p2dYEVA.mjs} +1 -1
- package/dist/{middleware-Blq0882d.js → middleware-B6IVTVL0.js} +1362 -1181
- package/dist/{middleware-E_0IH72m.mjs → middleware-FnITXhtz.mjs} +549 -368
- package/dist/{mod-JzkaEXsi.d.cts → mod-B3OzOobN.d.cts} +1 -1
- package/dist/{mod-B2h2KY0q.d.ts → mod-BYvPz_T0.d.ts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +2 -2
- package/dist/mod.d.ts +2 -2
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/{owner-Cgx2Dc7I.mjs → owner-BJp47LxN.mjs} +3 -3
- package/dist/{owner-szIZl4qI.cjs → owner-DCt8W_Cv.cjs} +1 -1
- package/dist/{owner-DAGSC5gX.js → owner-j5yrbXS1.js} +1 -1
- package/dist/{portable-key-Cn1WJSN1.mjs → portable-key-B5oKE_wZ.mjs} +1 -1
- package/dist/{proof-DVXC6nYz.mjs → proof-BE4T5zF6.mjs} +4 -4
- package/dist/{send-D-hOvYKy.mjs → send-BFITuKHw.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 +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +72 -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.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +2 -2
- package/dist/sig/portable-key.test.mjs +3 -3
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{sig-B5Ell7pO.js → sig-CJHdGk9w.js} +2 -2
- package/dist/{sig-CXOKN4Br.cjs → sig-CRTRoouH.cjs} +2 -2
- package/dist/{temporal-CqpKGEPk.mjs → temporal-CyDDbE4i.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +78 -13
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-CO_cEvQc.mjs → testing-BWq1dCzx.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,28 +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-CkjiYK_Z.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-BCAa5-kE.mjs";
|
|
6
6
|
import { t as formatAcceptSignature } from "./accept-CPVqki78.mjs";
|
|
7
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-
|
|
8
|
+
import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-I8gLBkcf.mjs";
|
|
9
9
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
|
|
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-
|
|
11
|
-
import { i as signObject, l as verifyPortableObjectProofWithRoot, o as verifyObject, r as hasProofLike, s as verifyPortableObjectProof, t as InvalidPortableObjectIdError } from "./proof-
|
|
12
|
-
import {
|
|
13
|
-
import { t as getAuthenticatedDocumentLoader } from "./docloader-
|
|
14
|
-
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-wiJbqJHa.mjs";
|
|
11
|
+
import { i as signObject, l as verifyPortableObjectProofWithRoot, o as verifyObject, r as hasProofLike, s as verifyPortableObjectProof, t as InvalidPortableObjectIdError } from "./proof-BE4T5zF6.mjs";
|
|
12
|
+
import { a as listRequestSignatures, c as selectRequestSignature, d as verifyRequest, f as verifyRequestDetailed, s as parseRfc9421SignatureInput } from "./http-BU-8fUrz.mjs";
|
|
13
|
+
import { t as getAuthenticatedDocumentLoader } from "./docloader-C1hAJnWc.mjs";
|
|
14
|
+
import { n as kvCache } from "./kv-cache-DJFoGRWQ.mjs";
|
|
15
15
|
import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-DpnpgjyX.mjs";
|
|
16
16
|
import { t as nodeInfoToJson } from "./types-DE_LV1JB.mjs";
|
|
17
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
18
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-
|
|
19
|
-
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-
|
|
17
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-BJp47LxN.mjs";
|
|
18
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-BFITuKHw.mjs";
|
|
19
|
+
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-CHht75pX.mjs";
|
|
20
20
|
import { t as CircuitBreaker } from "./circuit-breaker-C5NqoLBJ.mjs";
|
|
21
21
|
import { t as buildCollectionSynchronizationHeader } from "./collection-CnnP1UAv.mjs";
|
|
22
|
-
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-DKKH_Nr1.mjs";
|
|
23
23
|
import { t as KvKeyCache } from "./keycache-BUDHB5rY.mjs";
|
|
24
24
|
import { t as acceptsJsonLd } from "./negotiation-CCocWT_N.mjs";
|
|
25
|
-
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-
|
|
25
|
+
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CyDDbE4i.mjs";
|
|
26
26
|
import { t as createExponentialBackoffPolicy } from "./retry-zX5xw5e0.mjs";
|
|
27
27
|
import { t as TaskCodec } from "./codec-C6rTrpIp.mjs";
|
|
28
28
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
@@ -522,6 +522,8 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
522
522
|
const PORTABLE_OBJECT_CONTENT_TYPE = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
|
|
523
523
|
const GATEWAY_OBJECT_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
524
524
|
const BARE_DID_PATTERN = /^did:[a-z0-9]+:[^/?#]+$/i;
|
|
525
|
+
const DID_KEY_PREFIX = "did:key:";
|
|
526
|
+
const BASE58BTC_MULTIBASE_PATTERN = /^z[1-9A-HJ-NP-Za-km-z]+$/;
|
|
525
527
|
const GATEWAY_PATH_PREFIX = "/.well-known/apgateway/";
|
|
526
528
|
const HASHLINK_SCHEME_PATTERN = /^hl(?::|%3A)/i;
|
|
527
529
|
/**
|
|
@@ -557,6 +559,15 @@ function parsePortableGatewayRequest(url) {
|
|
|
557
559
|
throw error;
|
|
558
560
|
}
|
|
559
561
|
const authority = getFe34Origin(id);
|
|
562
|
+
try {
|
|
563
|
+
assertBase58BtcDidKey(authority);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
if (error instanceof TypeError) return {
|
|
566
|
+
type: "malformed",
|
|
567
|
+
error
|
|
568
|
+
};
|
|
569
|
+
throw error;
|
|
570
|
+
}
|
|
560
571
|
const href = id.href;
|
|
561
572
|
return {
|
|
562
573
|
type: "object",
|
|
@@ -575,12 +586,26 @@ function parsePortableGatewayRequest(url) {
|
|
|
575
586
|
* @param authority The bare DID, e.g., `did:key:z6Mk...`.
|
|
576
587
|
* @param path The object path, e.g., `/notes/123`.
|
|
577
588
|
* @returns The portable ID.
|
|
578
|
-
* @throws {TypeError} If the authority is not a bare DID
|
|
589
|
+
* @throws {TypeError} If the authority is not a bare DID, or it is
|
|
590
|
+
* a `did:key` DID that is not encoded in base58-btc.
|
|
579
591
|
*/
|
|
580
592
|
function buildPortableUri(authority, path) {
|
|
581
593
|
if (typeof authority !== "string" || !BARE_DID_PATTERN.test(authority)) throw new TypeError("The authority of a portable ID must be a DID without a path, query, or fragment.");
|
|
582
|
-
getFe34Origin(authority);
|
|
583
|
-
|
|
594
|
+
const did = getFe34Origin(authority);
|
|
595
|
+
assertBase58BtcDidKey(did);
|
|
596
|
+
return parseIri(`ap+ef61://${did}${path}`);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Checks that a normalized DID, if it is a `did:key` DID, is encoded in
|
|
600
|
+
* base58-btc, as FEP-ef61 requires so that the same key does not yield two
|
|
601
|
+
* portable IDs. This only checks the encoding, not whether the DID is
|
|
602
|
+
* a valid key.
|
|
603
|
+
* @param did The DID normalized by `getFe34Origin()`.
|
|
604
|
+
* @throws {TypeError} If the DID is a `did:key` DID that is not encoded in
|
|
605
|
+
* base58-btc.
|
|
606
|
+
*/
|
|
607
|
+
function assertBase58BtcDidKey(did) {
|
|
608
|
+
if (did.startsWith(DID_KEY_PREFIX) && !BASE58BTC_MULTIBASE_PATTERN.test(did.slice(8))) throw new TypeError("The did:key authority of a portable ID must be encoded in base58-btc, i.e., start with z.");
|
|
584
609
|
}
|
|
585
610
|
/**
|
|
586
611
|
* Recognizes an FEP-ef61 gateway request for a resource addressed by
|
|
@@ -632,6 +657,415 @@ function parseHashlinkGatewayRequest(url) {
|
|
|
632
657
|
};
|
|
633
658
|
}
|
|
634
659
|
//#endregion
|
|
660
|
+
//#region src/federation/portable-inbox.ts
|
|
661
|
+
const MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
662
|
+
/**
|
|
663
|
+
* Fills in the defaults of {@link PortableInboxForwardingOptions} and
|
|
664
|
+
* validates them.
|
|
665
|
+
* @param options The options.
|
|
666
|
+
* @returns The resolved options.
|
|
667
|
+
* @throws {RangeError} If `maxTargets` is not a non-negative integer, if `ttl`
|
|
668
|
+
* is not positive, if `deadline` is negative or longer
|
|
669
|
+
* than 2,147,483,647 milliseconds (about 24.8 days), or
|
|
670
|
+
* if either duration has calendar units, i.e., weeks,
|
|
671
|
+
* months, or years, whose length depends on the date.
|
|
672
|
+
*/
|
|
673
|
+
function resolvePortableInboxForwardingOptions(options = {}) {
|
|
674
|
+
const maxTargets = options.maxTargets ?? 10;
|
|
675
|
+
if (!Number.isInteger(maxTargets) || maxTargets < 0) throw new RangeError("portableInboxForwarding.maxTargets must be a non-negative integer.");
|
|
676
|
+
const ttl = Temporal.Duration.from(options.ttl ?? { days: 30 });
|
|
677
|
+
if (ttl.total("millisecond") <= 0) throw new RangeError("portableInboxForwarding.ttl must be positive.");
|
|
678
|
+
const deadline = Temporal.Duration.from(options.deadline ?? { seconds: 10 });
|
|
679
|
+
const deadlineMs = deadline.total("millisecond");
|
|
680
|
+
if (deadlineMs < 0 || deadlineMs > MAX_TIMEOUT_MS) throw new RangeError(`portableInboxForwarding.deadline must be between 0 and ${MAX_TIMEOUT_MS} milliseconds.`);
|
|
681
|
+
return {
|
|
682
|
+
maxTargets,
|
|
683
|
+
ttl,
|
|
684
|
+
deadline
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Decides whether this server accepts a delivery to a portable inbox on behalf
|
|
689
|
+
* of the given actor, which the actor dispatcher returned for the identifier
|
|
690
|
+
* in the requested inbox path.
|
|
691
|
+
*
|
|
692
|
+
* The actor document comes from the application, so it is trusted as is and
|
|
693
|
+
* its proof is not verified here.
|
|
694
|
+
* @param actor The actor the actor dispatcher returned.
|
|
695
|
+
* @param options The requested DID, the canonical form of the requested inbox
|
|
696
|
+
* ID, and the origin of this server.
|
|
697
|
+
* @returns The resolution.
|
|
698
|
+
*/
|
|
699
|
+
function resolvePortableInboxRecipient(actor, { authority, canonicalInboxId, localOrigin }) {
|
|
700
|
+
if (actor == null) return {
|
|
701
|
+
status: "rejected",
|
|
702
|
+
reason: "notFound"
|
|
703
|
+
};
|
|
704
|
+
const id = actor.id;
|
|
705
|
+
const did = id == null || !isPortableId(id) ? null : getPortableDid(id);
|
|
706
|
+
if (id == null || did == null) return {
|
|
707
|
+
status: "rejected",
|
|
708
|
+
reason: "notPortable"
|
|
709
|
+
};
|
|
710
|
+
if (did !== authority) return {
|
|
711
|
+
status: "rejected",
|
|
712
|
+
reason: "authorityMismatch"
|
|
713
|
+
};
|
|
714
|
+
const inboxId = actor.inboxId == null ? null : toPortableUri(actor.inboxId);
|
|
715
|
+
if (inboxId == null || getCanonicalPortableId(inboxId) !== canonicalInboxId) return {
|
|
716
|
+
status: "rejected",
|
|
717
|
+
reason: "inboxMismatch"
|
|
718
|
+
};
|
|
719
|
+
const gateways = actor.gateways;
|
|
720
|
+
if (!gateways.some((g) => isGatewayUrl(g) && g.origin === localOrigin)) return {
|
|
721
|
+
status: "rejected",
|
|
722
|
+
reason: "notGateway"
|
|
723
|
+
};
|
|
724
|
+
return {
|
|
725
|
+
status: "accepted",
|
|
726
|
+
recipient: {
|
|
727
|
+
actorId: id,
|
|
728
|
+
inboxId,
|
|
729
|
+
canonicalInboxId,
|
|
730
|
+
gateways
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Gets the portable form of an `ap:` or `ap+ef61:` URI or a compatible
|
|
736
|
+
* identifier, keeping its query.
|
|
737
|
+
* @returns The portable URI, or `null` if the ID is neither, or is malformed.
|
|
738
|
+
*/
|
|
739
|
+
function toPortableUri(id) {
|
|
740
|
+
if (isPortableUri(id)) return id;
|
|
741
|
+
try {
|
|
742
|
+
return fromCompatibleEf61Id(id);
|
|
743
|
+
} catch (error) {
|
|
744
|
+
if (error instanceof TypeError) return null;
|
|
745
|
+
throw error;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const RFC9421_DELIVERY_COMPONENTS = [
|
|
749
|
+
"@method",
|
|
750
|
+
"@target-uri",
|
|
751
|
+
"content-digest"
|
|
752
|
+
];
|
|
753
|
+
const DRAFT_DELIVERY_COMPONENTS = [
|
|
754
|
+
"(request-target)",
|
|
755
|
+
"host",
|
|
756
|
+
"digest"
|
|
757
|
+
];
|
|
758
|
+
/**
|
|
759
|
+
* Checks whether an HTTP Signature covers the method, the target URI, and the
|
|
760
|
+
* body of a request, which both Fedify and Mitra sign when forwarding.
|
|
761
|
+
* @param signature The signature.
|
|
762
|
+
* @returns `true` if the signature covers them.
|
|
763
|
+
*/
|
|
764
|
+
function coversDelivery(signature) {
|
|
765
|
+
return (signature.label == null ? DRAFT_DELIVERY_COMPONENTS : RFC9421_DELIVERY_COMPONENTS).every((name) => signature.components.some((c) => c.value === name && Object.keys(c.params).length < 1));
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Forwards an activity received in a portable inbox to the inboxes of the same
|
|
769
|
+
* portable actor on its other gateways, as FEP-ef61 recommends.
|
|
770
|
+
*
|
|
771
|
+
* FEP-ef61 forbids forwarding an activity from an inbox more than once, so
|
|
772
|
+
* each target gateway is claimed with an atomic compare-and-swap before the
|
|
773
|
+
* activity is handed off, and a claim is never released, even if the hand-off
|
|
774
|
+
* fails, since a failed hand-off does not prove that nothing was delivered.
|
|
775
|
+
* Forwarding is therefore best effort. Without {@link KvStore.cas}, nothing is
|
|
776
|
+
* forwarded.
|
|
777
|
+
*
|
|
778
|
+
* Forwarded requests are signed with HTTP Signatures by the RSA key among
|
|
779
|
+
* {@link ForwardPortableInboxActivityParameters.getKeys}, if any, so that
|
|
780
|
+
* servers requiring HTTP Signatures accept them, and are sent unsigned
|
|
781
|
+
* otherwise. Either way, the receiving gateways authenticate the activity by
|
|
782
|
+
* its own proof.
|
|
783
|
+
*
|
|
784
|
+
* If the delivery is signed with a gateway key of the recipient by one of its
|
|
785
|
+
* other gateways, that gateway has the activity already, so it is claimed
|
|
786
|
+
* without forwarding the activity to it. See {@link identifySendingGateway}.
|
|
787
|
+
* @param parameters The parameters.
|
|
788
|
+
* @returns The target inbox URLs that the activity was handed off to.
|
|
789
|
+
*/
|
|
790
|
+
async function forwardPortableInboxActivity(parameters) {
|
|
791
|
+
const logger = getLogger([
|
|
792
|
+
"fedify",
|
|
793
|
+
"federation",
|
|
794
|
+
"inbox"
|
|
795
|
+
]);
|
|
796
|
+
const { recipient, activityId, excludedOrigins, kv, kvPrefix, outboxQueue } = parameters;
|
|
797
|
+
const options = parameters.options ?? resolvePortableInboxForwardingOptions();
|
|
798
|
+
const { maxTargets, ttl } = options;
|
|
799
|
+
if (kv.cas == null || maxTargets < 1) return [];
|
|
800
|
+
const deadline = Date.now() + options.deadline.total("millisecond");
|
|
801
|
+
const canonicalActivityId = getCanonicalPortableId(activityId) ?? activityId.href;
|
|
802
|
+
const claimKey = (gateway) => [
|
|
803
|
+
...kvPrefix,
|
|
804
|
+
recipient.canonicalInboxId,
|
|
805
|
+
canonicalActivityId,
|
|
806
|
+
gateway
|
|
807
|
+
];
|
|
808
|
+
let targets = getForwardingTargets(recipient, excludedOrigins);
|
|
809
|
+
const sender = await identifySendingGateway(parameters, targets.map((t) => t.gateway), claimKey, deadline);
|
|
810
|
+
if (sender != null) {
|
|
811
|
+
logger.debug("Not forwarding activity {activityId} from the portable inbox {inbox} back to the gateway {gateway}, which delivered it.", {
|
|
812
|
+
activityId: activityId.href,
|
|
813
|
+
inbox: recipient.canonicalInboxId,
|
|
814
|
+
gateway: sender
|
|
815
|
+
});
|
|
816
|
+
await kv.cas(claimKey(sender), void 0, true, { ttl });
|
|
817
|
+
targets = targets.filter((t) => t.gateway !== sender);
|
|
818
|
+
}
|
|
819
|
+
if (targets.length > maxTargets) {
|
|
820
|
+
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.", {
|
|
821
|
+
inbox: recipient.canonicalInboxId,
|
|
822
|
+
activityId: activityId.href,
|
|
823
|
+
max: maxTargets
|
|
824
|
+
});
|
|
825
|
+
targets.length = maxTargets;
|
|
826
|
+
}
|
|
827
|
+
const claimed = [];
|
|
828
|
+
for (const { gateway, inbox } of targets) if (await kv.cas(claimKey(gateway), void 0, true, { ttl })) claimed.push(inbox);
|
|
829
|
+
if (claimed.length < 1) return [];
|
|
830
|
+
logger.debug("Forwarding activity {activityId} from the portable inbox {inbox} to other gateways:\n{targets}", {
|
|
831
|
+
activityId: activityId.href,
|
|
832
|
+
inbox: recipient.canonicalInboxId,
|
|
833
|
+
targets: claimed.map((t) => t.href)
|
|
834
|
+
});
|
|
835
|
+
if (outboxQueue == null) await forwardImmediately(parameters, claimed, deadline);
|
|
836
|
+
else await enqueueForwarding(parameters, outboxQueue, claimed);
|
|
837
|
+
return claimed;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Identifies the gateway of the recipient that forwarded a delivery to its
|
|
841
|
+
* portable inbox, by the delivery's HTTP Signature made with the recipient's
|
|
842
|
+
* gateway key, which FEP-ef61 has each gateway use for requests on behalf of
|
|
843
|
+
* an actor. Since the activity is authenticated by its own proof, the
|
|
844
|
+
* signature only tells which gateway to skip, and a delivery whose signature
|
|
845
|
+
* is invalid or cannot be verified is still forwarded to every gateway.
|
|
846
|
+
*
|
|
847
|
+
* Verifying the signature fetches the recipient's actor document from the
|
|
848
|
+
* sending gateway, as gateway keys are not cached, so only the first signature
|
|
849
|
+
* that may save a request is verified: one that covers the delivery and names
|
|
850
|
+
* a gateway key of the recipient on one of the forwarding targets that the
|
|
851
|
+
* activity has not been forwarded to yet.
|
|
852
|
+
* @param parameters The forwarding parameters.
|
|
853
|
+
* @param targets The origins of the gateways to forward to.
|
|
854
|
+
* @param claimKey Gets the key of the claim for a gateway.
|
|
855
|
+
* @param deadline The time by which identification has to finish, in
|
|
856
|
+
* milliseconds since the epoch; afterwards, the sending
|
|
857
|
+
* gateway is left unidentified.
|
|
858
|
+
* @returns The origin of the sending gateway, or `null` if it is not
|
|
859
|
+
* identified.
|
|
860
|
+
*/
|
|
861
|
+
async function identifySendingGateway({ recipient, activityId, kv, signatures }, targets, claimKey, deadline) {
|
|
862
|
+
if (signatures == null || signatures.length < 1) return null;
|
|
863
|
+
const logger = getLogger([
|
|
864
|
+
"fedify",
|
|
865
|
+
"federation",
|
|
866
|
+
"inbox"
|
|
867
|
+
]);
|
|
868
|
+
const actorId = getCanonicalPortableId(recipient.actorId);
|
|
869
|
+
if (actorId == null) return null;
|
|
870
|
+
const isGatewayKeyOfRecipient = (keyId) => {
|
|
871
|
+
if (!isCompatibleKeyId(keyId) || !targets.includes(keyId.origin)) return false;
|
|
872
|
+
const base = new URL(keyId.href);
|
|
873
|
+
base.hash = "";
|
|
874
|
+
return getCanonicalPortableId(base) === actorId;
|
|
875
|
+
};
|
|
876
|
+
let signature;
|
|
877
|
+
for (const candidate of signatures) if (candidate.coversDelivery && isGatewayKeyOfRecipient(candidate.keyId) && await kv.get(claimKey(candidate.keyId.origin)) === void 0) {
|
|
878
|
+
signature = candidate;
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
if (signature == null) return null;
|
|
882
|
+
const { keyId } = signature;
|
|
883
|
+
const logProperties = {
|
|
884
|
+
activityId: activityId.href,
|
|
885
|
+
inbox: recipient.canonicalInboxId,
|
|
886
|
+
keyId: keyId.href
|
|
887
|
+
};
|
|
888
|
+
const verification = Promise.resolve().then(() => signature.verify()).then((key) => {
|
|
889
|
+
if (key == null) logger.debug("The HTTP Signature of the delivery of activity {activityId} to the portable inbox {inbox} is not verified with the key {keyId}; forwarding it without identifying the sending gateway.", logProperties);
|
|
890
|
+
return key;
|
|
891
|
+
}, (error) => {
|
|
892
|
+
logger.debug("Failed to verify the HTTP Signature of the delivery of activity {activityId} to the portable inbox {inbox} with the key {keyId}; forwarding it without identifying the sending gateway:\n{error}", {
|
|
893
|
+
...logProperties,
|
|
894
|
+
error
|
|
895
|
+
});
|
|
896
|
+
return null;
|
|
897
|
+
});
|
|
898
|
+
let timer;
|
|
899
|
+
const key = await Promise.race([verification, new Promise((resolve) => {
|
|
900
|
+
timer = setTimeout(() => {
|
|
901
|
+
logger.debug("Verifying the HTTP Signature of the delivery of activity {activityId} to the portable inbox {inbox} did not finish in time; forwarding it without identifying the sending gateway.", logProperties);
|
|
902
|
+
resolve(null);
|
|
903
|
+
}, Math.max(0, deadline - Date.now()));
|
|
904
|
+
})]);
|
|
905
|
+
clearTimeout(timer);
|
|
906
|
+
if (key == null) return null;
|
|
907
|
+
if (key.id == null || key.id.href !== keyId.href || !isGatewayKeyOfRecipient(key.id) || key.ownerId == null || !isPortableId(key.ownerId) || getCanonicalPortableId(key.ownerId) !== actorId) {
|
|
908
|
+
logger.debug("The delivery of activity {activityId} to the portable inbox {inbox} is not signed with a gateway key of the recipient; forwarding it without identifying the sending gateway.", logProperties);
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
return key.id.origin;
|
|
912
|
+
}
|
|
913
|
+
function getForwardingTargets(recipient, excludedOrigins) {
|
|
914
|
+
const logger = getLogger([
|
|
915
|
+
"fedify",
|
|
916
|
+
"federation",
|
|
917
|
+
"inbox"
|
|
918
|
+
]);
|
|
919
|
+
const seen = new Set(excludedOrigins);
|
|
920
|
+
const targets = [];
|
|
921
|
+
for (const gateway of recipient.gateways) {
|
|
922
|
+
if (!isGatewayUrl(gateway) || seen.has(gateway.origin)) continue;
|
|
923
|
+
seen.add(gateway.origin);
|
|
924
|
+
let inbox;
|
|
925
|
+
try {
|
|
926
|
+
inbox = toCompatibleEf61Id(recipient.inboxId, gateway.origin);
|
|
927
|
+
} catch (error) {
|
|
928
|
+
if (!(error instanceof TypeError)) throw error;
|
|
929
|
+
logger.warn("Cannot forward to the gateway {gateway}, as the portable inbox {inbox} cannot be represented as its compatible identifier.", {
|
|
930
|
+
gateway: gateway.origin,
|
|
931
|
+
inbox: recipient.canonicalInboxId
|
|
932
|
+
});
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
targets.push({
|
|
936
|
+
gateway: gateway.origin,
|
|
937
|
+
inbox
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
return targets;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Resolves the key that signs forwarded requests: the first RSASSA-PKCS1-v1_5
|
|
944
|
+
* key, as {@link sendActivity} signs requests only with such a key. Since
|
|
945
|
+
* the targets have already been claimed, the activity is forwarded unsigned
|
|
946
|
+
* rather than not at all if the key cannot be resolved or used.
|
|
947
|
+
*/
|
|
948
|
+
async function resolveForwardingKey({ getKeys, activityId }) {
|
|
949
|
+
if (getKeys == null) return null;
|
|
950
|
+
const logger = getLogger([
|
|
951
|
+
"fedify",
|
|
952
|
+
"federation",
|
|
953
|
+
"inbox"
|
|
954
|
+
]);
|
|
955
|
+
let key;
|
|
956
|
+
try {
|
|
957
|
+
key = (await getKeys()).find((k) => k.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5");
|
|
958
|
+
} catch (error) {
|
|
959
|
+
logger.error("Failed to get the gateway keys to sign forwarded requests with; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
|
|
960
|
+
activityId: activityId.href,
|
|
961
|
+
error
|
|
962
|
+
});
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
if (key == null) return null;
|
|
966
|
+
try {
|
|
967
|
+
validateCryptoKey(key.privateKey, "private");
|
|
968
|
+
} catch (error) {
|
|
969
|
+
if (!(error instanceof TypeError)) throw error;
|
|
970
|
+
logger.warn("The gateway key {keyId} cannot sign forwarded requests; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
|
|
971
|
+
keyId: key.keyId.href,
|
|
972
|
+
activityId: activityId.href,
|
|
973
|
+
error
|
|
974
|
+
});
|
|
975
|
+
return null;
|
|
976
|
+
}
|
|
977
|
+
return key;
|
|
978
|
+
}
|
|
979
|
+
async function forwardImmediately(parameters, inboxes, deadlineAt) {
|
|
980
|
+
const { activity, activityId, activityType, allowPrivateAddress, specDeterminer, meterProvider, tracerProvider } = parameters;
|
|
981
|
+
const logger = getLogger([
|
|
982
|
+
"fedify",
|
|
983
|
+
"federation",
|
|
984
|
+
"inbox"
|
|
985
|
+
]);
|
|
986
|
+
const deadline = Math.max(0, deadlineAt - Date.now());
|
|
987
|
+
const forwarding = resolveForwardingKey(parameters).then((key) => Promise.all(inboxes.map((inbox) => sendActivity({
|
|
988
|
+
activity,
|
|
989
|
+
activityId: activityId.href,
|
|
990
|
+
activityType,
|
|
991
|
+
keys: key == null ? [] : [key],
|
|
992
|
+
inbox,
|
|
993
|
+
allowPrivateAddress,
|
|
994
|
+
specDeterminer,
|
|
995
|
+
meterProvider,
|
|
996
|
+
tracerProvider
|
|
997
|
+
}).catch((error) => {
|
|
998
|
+
logger.error("Failed to forward activity {activityId} to {inbox}; it will not be forwarded to the inbox again:\n{error}", {
|
|
999
|
+
activityId: activityId.href,
|
|
1000
|
+
inbox: inbox.href,
|
|
1001
|
+
error
|
|
1002
|
+
});
|
|
1003
|
+
}))));
|
|
1004
|
+
let timer;
|
|
1005
|
+
const timedOut = await Promise.race([forwarding.then(() => false), new Promise((resolve) => {
|
|
1006
|
+
timer = setTimeout(() => resolve(true), deadline);
|
|
1007
|
+
})]);
|
|
1008
|
+
clearTimeout(timer);
|
|
1009
|
+
if (timedOut) logger.warn("Forwarding activity {activityId} to other gateways did not finish in {deadline} ms; responding to the delivery without waiting for it.", {
|
|
1010
|
+
activityId: activityId.href,
|
|
1011
|
+
deadline
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
async function enqueueForwarding(parameters, outboxQueue, inboxes) {
|
|
1015
|
+
const { activity, activityId, activityType, baseUrl, startQueue, meterProvider } = parameters;
|
|
1016
|
+
const logger = getLogger([
|
|
1017
|
+
"fedify",
|
|
1018
|
+
"federation",
|
|
1019
|
+
"inbox"
|
|
1020
|
+
]);
|
|
1021
|
+
const key = await resolveForwardingKey(parameters);
|
|
1022
|
+
const keys = [];
|
|
1023
|
+
if (key != null) try {
|
|
1024
|
+
keys.push({
|
|
1025
|
+
keyId: key.keyId.href,
|
|
1026
|
+
privateKey: await exportJwk(key.privateKey)
|
|
1027
|
+
});
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
logger.error("Failed to export the gateway key {keyId}; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
|
|
1030
|
+
keyId: key.keyId.href,
|
|
1031
|
+
activityId: activityId.href,
|
|
1032
|
+
error
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
startQueue?.();
|
|
1036
|
+
const started = (/* @__PURE__ */ new Date()).toISOString();
|
|
1037
|
+
const traceContext = {};
|
|
1038
|
+
propagation.inject(context.active(), traceContext);
|
|
1039
|
+
await Promise.all(inboxes.map(async (inbox) => {
|
|
1040
|
+
const message = {
|
|
1041
|
+
type: "outbox",
|
|
1042
|
+
id: crypto.randomUUID(),
|
|
1043
|
+
baseUrl,
|
|
1044
|
+
keys,
|
|
1045
|
+
activity,
|
|
1046
|
+
activityId: activityId.href,
|
|
1047
|
+
activityType,
|
|
1048
|
+
inbox: inbox.href,
|
|
1049
|
+
sharedInbox: false,
|
|
1050
|
+
started,
|
|
1051
|
+
attempt: 0,
|
|
1052
|
+
headers: {},
|
|
1053
|
+
traceContext
|
|
1054
|
+
};
|
|
1055
|
+
try {
|
|
1056
|
+
await outboxQueue.enqueue(message);
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
logger.error("Failed to enqueue activity {activityId} to forward to {inbox}; it will not be forwarded to the inbox again:\n{error}", {
|
|
1059
|
+
activityId: activityId.href,
|
|
1060
|
+
inbox: inbox.href,
|
|
1061
|
+
error
|
|
1062
|
+
});
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
recordOutboxEnqueue(meterProvider, outboxQueue, message);
|
|
1066
|
+
}));
|
|
1067
|
+
}
|
|
1068
|
+
//#endregion
|
|
635
1069
|
//#region src/federation/handler.ts
|
|
636
1070
|
const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
|
|
637
1071
|
const INBOX_COMPOUND_PROOF_LIMITS = {
|
|
@@ -729,8 +1163,9 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
729
1163
|
}
|
|
730
1164
|
/**
|
|
731
1165
|
* Handles an FEP-ef61 gateway request for a portable object through an object
|
|
732
|
-
* dispatcher
|
|
733
|
-
*
|
|
1166
|
+
* dispatcher, or the actor dispatcher adapted to one. The object is served
|
|
1167
|
+
* only if its ID canonically matches the requested portable ID and it
|
|
1168
|
+
* satisfies the FEP-ef61 proof policy.
|
|
734
1169
|
* @template TContextData The context data to pass to the context.
|
|
735
1170
|
* @param request The HTTP request.
|
|
736
1171
|
* @param parameters The parameters for handling the portable object.
|
|
@@ -1970,7 +2405,21 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1970
2405
|
inbox: portableInbox.recipient.canonicalInboxId
|
|
1971
2406
|
});
|
|
1972
2407
|
else try {
|
|
1973
|
-
await portableInbox.forward(json, activity.id, getTypeId(activity).href)
|
|
2408
|
+
await portableInbox.forward(json, activity.id, getTypeId(activity).href, listRequestSignatures(request).map((signature) => ({
|
|
2409
|
+
keyId: signature.keyId,
|
|
2410
|
+
coversDelivery: coversDelivery(signature),
|
|
2411
|
+
verify: async () => {
|
|
2412
|
+
const verification = await verifyRequestDetailed(selectRequestSignature(request, signature), {
|
|
2413
|
+
contextLoader: ctx.contextLoader,
|
|
2414
|
+
documentLoader: ctx.documentLoader,
|
|
2415
|
+
timeWindow: signatureTimeWindow,
|
|
2416
|
+
keyCache,
|
|
2417
|
+
meterProvider,
|
|
2418
|
+
tracerProvider
|
|
2419
|
+
});
|
|
2420
|
+
return verification.verified ? verification.key : null;
|
|
2421
|
+
}
|
|
2422
|
+
})));
|
|
1974
2423
|
} catch (error) {
|
|
1975
2424
|
logger.error("Failed to forward activity {activityId} to the other gateways of the portable inbox {inbox}:\n{error}", {
|
|
1976
2425
|
activityId: activity.id.href,
|
|
@@ -2603,363 +3052,61 @@ async function buildAcceptSignatureHeader(policy, kv, noncePrefix) {
|
|
|
2603
3052
|
async function setKey(kv, key, policy) {
|
|
2604
3053
|
const seconds = policy.nonceTtlSeconds ?? 300;
|
|
2605
3054
|
const ttl = Temporal.Duration.from({ seconds });
|
|
2606
|
-
await kv.set(key, true, { ttl });
|
|
2607
|
-
}
|
|
2608
|
-
const DEF_COMPONENTS = [
|
|
2609
|
-
"@method",
|
|
2610
|
-
"@target-uri",
|
|
2611
|
-
"@authority",
|
|
2612
|
-
"content-digest"
|
|
2613
|
-
];
|
|
2614
|
-
const MIN_COMPONENTS = [
|
|
2615
|
-
"@method",
|
|
2616
|
-
"@target-uri",
|
|
2617
|
-
"@authority"
|
|
2618
|
-
];
|
|
2619
|
-
/**
|
|
2620
|
-
* Checks whether an HTTP Signature key is a gateway key of an FEP-ef61
|
|
2621
|
-
* portable actor, i.e., its ID is a compatible identifier and it is owned by
|
|
2622
|
-
* a portable actor, which only the portable actor's signed document can
|
|
2623
|
-
* establish.
|
|
2624
|
-
*/
|
|
2625
|
-
function isPortableGatewayKey(key) {
|
|
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;
|
|
2661
|
-
}
|
|
2662
|
-
//#endregion
|
|
2663
|
-
//#region src/federation/portable-inbox.ts
|
|
2664
|
-
const MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
2665
|
-
/**
|
|
2666
|
-
* Fills in the defaults of {@link PortableInboxForwardingOptions} and
|
|
2667
|
-
* validates them.
|
|
2668
|
-
* @param options The options.
|
|
2669
|
-
* @returns The resolved options.
|
|
2670
|
-
* @throws {RangeError} If `maxTargets` is not a non-negative integer, if `ttl`
|
|
2671
|
-
* is not positive, if `deadline` is negative or longer
|
|
2672
|
-
* than 2,147,483,647 milliseconds (about 24.8 days), or
|
|
2673
|
-
* if either duration has calendar units, i.e., weeks,
|
|
2674
|
-
* months, or years, whose length depends on the date.
|
|
2675
|
-
*/
|
|
2676
|
-
function resolvePortableInboxForwardingOptions(options = {}) {
|
|
2677
|
-
const maxTargets = options.maxTargets ?? 10;
|
|
2678
|
-
if (!Number.isInteger(maxTargets) || maxTargets < 0) throw new RangeError("portableInboxForwarding.maxTargets must be a non-negative integer.");
|
|
2679
|
-
const ttl = Temporal.Duration.from(options.ttl ?? { days: 30 });
|
|
2680
|
-
if (ttl.total("millisecond") <= 0) throw new RangeError("portableInboxForwarding.ttl must be positive.");
|
|
2681
|
-
const deadline = Temporal.Duration.from(options.deadline ?? { seconds: 10 });
|
|
2682
|
-
const deadlineMs = deadline.total("millisecond");
|
|
2683
|
-
if (deadlineMs < 0 || deadlineMs > MAX_TIMEOUT_MS) throw new RangeError(`portableInboxForwarding.deadline must be between 0 and ${MAX_TIMEOUT_MS} milliseconds.`);
|
|
2684
|
-
return {
|
|
2685
|
-
maxTargets,
|
|
2686
|
-
ttl,
|
|
2687
|
-
deadline
|
|
2688
|
-
};
|
|
2689
|
-
}
|
|
2690
|
-
/**
|
|
2691
|
-
* Decides whether this server accepts a delivery to a portable inbox on behalf
|
|
2692
|
-
* of the given actor, which the actor dispatcher returned for the identifier
|
|
2693
|
-
* in the requested inbox path.
|
|
2694
|
-
*
|
|
2695
|
-
* The actor document comes from the application, so it is trusted as is and
|
|
2696
|
-
* its proof is not verified here.
|
|
2697
|
-
* @param actor The actor the actor dispatcher returned.
|
|
2698
|
-
* @param options The requested DID, the canonical form of the requested inbox
|
|
2699
|
-
* ID, and the origin of this server.
|
|
2700
|
-
* @returns The resolution.
|
|
2701
|
-
*/
|
|
2702
|
-
function resolvePortableInboxRecipient(actor, { authority, canonicalInboxId, localOrigin }) {
|
|
2703
|
-
if (actor == null) return {
|
|
2704
|
-
status: "rejected",
|
|
2705
|
-
reason: "notFound"
|
|
2706
|
-
};
|
|
2707
|
-
const id = actor.id;
|
|
2708
|
-
const did = id == null || !isPortableId(id) ? null : getPortableDid(id);
|
|
2709
|
-
if (id == null || did == null) return {
|
|
2710
|
-
status: "rejected",
|
|
2711
|
-
reason: "notPortable"
|
|
2712
|
-
};
|
|
2713
|
-
if (did !== authority) return {
|
|
2714
|
-
status: "rejected",
|
|
2715
|
-
reason: "authorityMismatch"
|
|
2716
|
-
};
|
|
2717
|
-
const inboxId = actor.inboxId == null ? null : toPortableUri(actor.inboxId);
|
|
2718
|
-
if (inboxId == null || getCanonicalPortableId(inboxId) !== canonicalInboxId) return {
|
|
2719
|
-
status: "rejected",
|
|
2720
|
-
reason: "inboxMismatch"
|
|
2721
|
-
};
|
|
2722
|
-
const gateways = actor.gateways;
|
|
2723
|
-
if (!gateways.some((g) => isGatewayUrl(g) && g.origin === localOrigin)) return {
|
|
2724
|
-
status: "rejected",
|
|
2725
|
-
reason: "notGateway"
|
|
2726
|
-
};
|
|
2727
|
-
return {
|
|
2728
|
-
status: "accepted",
|
|
2729
|
-
recipient: {
|
|
2730
|
-
actorId: id,
|
|
2731
|
-
inboxId,
|
|
2732
|
-
canonicalInboxId,
|
|
2733
|
-
gateways
|
|
2734
|
-
}
|
|
2735
|
-
};
|
|
3055
|
+
await kv.set(key, true, { ttl });
|
|
2736
3056
|
}
|
|
3057
|
+
const DEF_COMPONENTS = [
|
|
3058
|
+
"@method",
|
|
3059
|
+
"@target-uri",
|
|
3060
|
+
"@authority",
|
|
3061
|
+
"content-digest"
|
|
3062
|
+
];
|
|
3063
|
+
const MIN_COMPONENTS = [
|
|
3064
|
+
"@method",
|
|
3065
|
+
"@target-uri",
|
|
3066
|
+
"@authority"
|
|
3067
|
+
];
|
|
2737
3068
|
/**
|
|
2738
|
-
*
|
|
2739
|
-
*
|
|
2740
|
-
*
|
|
3069
|
+
* Checks whether an HTTP Signature key is a gateway key of an FEP-ef61
|
|
3070
|
+
* portable actor, i.e., its ID is a compatible identifier and it is owned by
|
|
3071
|
+
* a portable actor, which only the portable actor's signed document can
|
|
3072
|
+
* establish.
|
|
2741
3073
|
*/
|
|
2742
|
-
function
|
|
2743
|
-
|
|
2744
|
-
try {
|
|
2745
|
-
return fromCompatibleEf61Id(id);
|
|
2746
|
-
} catch (error) {
|
|
2747
|
-
if (error instanceof TypeError) return null;
|
|
2748
|
-
throw error;
|
|
2749
|
-
}
|
|
3074
|
+
function isPortableGatewayKey(key) {
|
|
3075
|
+
return key?.id != null && isCompatibleKeyId(key.id) && key.ownerId != null && isPortableId(key.ownerId);
|
|
2750
3076
|
}
|
|
2751
3077
|
/**
|
|
2752
|
-
*
|
|
2753
|
-
*
|
|
2754
|
-
*
|
|
2755
|
-
* FEP-ef61 forbids forwarding an activity from an inbox more than once, so
|
|
2756
|
-
* each target gateway is claimed with an atomic compare-and-swap before the
|
|
2757
|
-
* activity is handed off, and a claim is never released, even if the hand-off
|
|
2758
|
-
* fails, since a failed hand-off does not prove that nothing was delivered.
|
|
2759
|
-
* Forwarding is therefore best effort. Without {@link KvStore.cas}, nothing is
|
|
2760
|
-
* forwarded.
|
|
3078
|
+
* Checks whether an activity needs an Object Integrity Proof by its DID:
|
|
3079
|
+
* either it is performed by an [FEP-ef61] portable actor, or its own ID is
|
|
3080
|
+
* portable. Both `ap:` URIs and compatible identifiers count as portable.
|
|
2761
3081
|
*
|
|
2762
|
-
*
|
|
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.
|
|
2767
|
-
* @param parameters The parameters.
|
|
2768
|
-
* @returns The target inbox URLs that the activity was handed off to.
|
|
3082
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
2769
3083
|
*/
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
"fedify",
|
|
2773
|
-
"federation",
|
|
2774
|
-
"inbox"
|
|
2775
|
-
]);
|
|
2776
|
-
const { recipient, activityId, excludedOrigins, kv, kvPrefix, outboxQueue } = parameters;
|
|
2777
|
-
const { maxTargets, ttl } = parameters.options ?? resolvePortableInboxForwardingOptions();
|
|
2778
|
-
if (kv.cas == null || maxTargets < 1) return [];
|
|
2779
|
-
const canonicalActivityId = getCanonicalPortableId(activityId) ?? activityId.href;
|
|
2780
|
-
const targets = getForwardingTargets(recipient, excludedOrigins);
|
|
2781
|
-
if (targets.length > maxTargets) {
|
|
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.", {
|
|
2783
|
-
inbox: recipient.canonicalInboxId,
|
|
2784
|
-
activityId: activityId.href,
|
|
2785
|
-
max: maxTargets
|
|
2786
|
-
});
|
|
2787
|
-
targets.length = maxTargets;
|
|
2788
|
-
}
|
|
2789
|
-
const claimed = [];
|
|
2790
|
-
for (const { gateway, inbox } of targets) {
|
|
2791
|
-
const key = [
|
|
2792
|
-
...kvPrefix,
|
|
2793
|
-
recipient.canonicalInboxId,
|
|
2794
|
-
canonicalActivityId,
|
|
2795
|
-
gateway
|
|
2796
|
-
];
|
|
2797
|
-
if (await kv.cas(key, void 0, true, { ttl })) claimed.push(inbox);
|
|
2798
|
-
}
|
|
2799
|
-
if (claimed.length < 1) return [];
|
|
2800
|
-
logger.debug("Forwarding activity {activityId} from the portable inbox {inbox} to other gateways:\n{targets}", {
|
|
2801
|
-
activityId: activityId.href,
|
|
2802
|
-
inbox: recipient.canonicalInboxId,
|
|
2803
|
-
targets: claimed.map((t) => t.href)
|
|
2804
|
-
});
|
|
2805
|
-
if (outboxQueue == null) await forwardImmediately(parameters, claimed);
|
|
2806
|
-
else await enqueueForwarding(parameters, outboxQueue, claimed);
|
|
2807
|
-
return claimed;
|
|
2808
|
-
}
|
|
2809
|
-
function getForwardingTargets(recipient, excludedOrigins) {
|
|
2810
|
-
const logger = getLogger([
|
|
2811
|
-
"fedify",
|
|
2812
|
-
"federation",
|
|
2813
|
-
"inbox"
|
|
2814
|
-
]);
|
|
2815
|
-
const seen = new Set(excludedOrigins);
|
|
2816
|
-
const targets = [];
|
|
2817
|
-
for (const gateway of recipient.gateways) {
|
|
2818
|
-
if (!isGatewayUrl(gateway) || seen.has(gateway.origin)) continue;
|
|
2819
|
-
seen.add(gateway.origin);
|
|
2820
|
-
let inbox;
|
|
2821
|
-
try {
|
|
2822
|
-
inbox = toCompatibleEf61Id(recipient.inboxId, gateway.origin);
|
|
2823
|
-
} catch (error) {
|
|
2824
|
-
if (!(error instanceof TypeError)) throw error;
|
|
2825
|
-
logger.warn("Cannot forward to the gateway {gateway}, as the portable inbox {inbox} cannot be represented as its compatible identifier.", {
|
|
2826
|
-
gateway: gateway.origin,
|
|
2827
|
-
inbox: recipient.canonicalInboxId
|
|
2828
|
-
});
|
|
2829
|
-
continue;
|
|
2830
|
-
}
|
|
2831
|
-
targets.push({
|
|
2832
|
-
gateway: gateway.origin,
|
|
2833
|
-
inbox
|
|
2834
|
-
});
|
|
2835
|
-
}
|
|
2836
|
-
return targets;
|
|
3084
|
+
function isPortableActivity(activity) {
|
|
3085
|
+
return hasPortableActor(activity) || activity.id != null && isPortableId(activity.id);
|
|
2837
3086
|
}
|
|
2838
3087
|
/**
|
|
2839
|
-
*
|
|
2840
|
-
*
|
|
2841
|
-
*
|
|
2842
|
-
*
|
|
3088
|
+
* Verifies that a portable activity's own ID belongs to the DID that signed
|
|
3089
|
+
* it, on the JSON-LD document received. The ID the proof policy checks has
|
|
3090
|
+
* to be the parsed activity's ID, so that a remote context that changes
|
|
3091
|
+
* between loads cannot make the two name different objects.
|
|
3092
|
+
* @returns `null` if the ID is verified, or the reason why it is not.
|
|
2843
3093
|
*/
|
|
2844
|
-
async function
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
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;
|
|
3094
|
+
async function verifyPortableActivityId(activityId, jsonLd, options) {
|
|
3095
|
+
const expectedId = getCanonicalPortableId(activityId);
|
|
3096
|
+
if (expectedId == null) return "malformed portable ID";
|
|
3097
|
+
let verification;
|
|
2862
3098
|
try {
|
|
2863
|
-
|
|
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;
|
|
2877
|
-
const logger = getLogger([
|
|
2878
|
-
"fedify",
|
|
2879
|
-
"federation",
|
|
2880
|
-
"inbox"
|
|
2881
|
-
]);
|
|
2882
|
-
const deadline = (options ?? resolvePortableInboxForwardingOptions()).deadline.total("millisecond");
|
|
2883
|
-
const forwarding = resolveForwardingKey(parameters).then((key) => Promise.all(inboxes.map((inbox) => sendActivity({
|
|
2884
|
-
activity,
|
|
2885
|
-
activityId: activityId.href,
|
|
2886
|
-
activityType,
|
|
2887
|
-
keys: key == null ? [] : [key],
|
|
2888
|
-
inbox,
|
|
2889
|
-
allowPrivateAddress,
|
|
2890
|
-
specDeterminer,
|
|
2891
|
-
meterProvider,
|
|
2892
|
-
tracerProvider
|
|
2893
|
-
}).catch((error) => {
|
|
2894
|
-
logger.error("Failed to forward activity {activityId} to {inbox}; it will not be forwarded to the inbox again:\n{error}", {
|
|
2895
|
-
activityId: activityId.href,
|
|
2896
|
-
inbox: inbox.href,
|
|
2897
|
-
error
|
|
2898
|
-
});
|
|
2899
|
-
}))));
|
|
2900
|
-
let timer;
|
|
2901
|
-
const timedOut = await Promise.race([forwarding.then(() => false), new Promise((resolve) => {
|
|
2902
|
-
timer = setTimeout(() => resolve(true), deadline);
|
|
2903
|
-
})]);
|
|
2904
|
-
clearTimeout(timer);
|
|
2905
|
-
if (timedOut) logger.warn("Forwarding activity {activityId} to other gateways did not finish in {deadline} ms; responding to the delivery without waiting for it.", {
|
|
2906
|
-
activityId: activityId.href,
|
|
2907
|
-
deadline
|
|
2908
|
-
});
|
|
2909
|
-
}
|
|
2910
|
-
async function enqueueForwarding(parameters, outboxQueue, inboxes) {
|
|
2911
|
-
const { activity, activityId, activityType, baseUrl, startQueue, meterProvider } = parameters;
|
|
2912
|
-
const logger = getLogger([
|
|
2913
|
-
"fedify",
|
|
2914
|
-
"federation",
|
|
2915
|
-
"inbox"
|
|
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
|
-
});
|
|
3099
|
+
verification = await verifyPortableObjectProofWithRoot(jsonLd, options);
|
|
2924
3100
|
} catch (error) {
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
error
|
|
2929
|
-
});
|
|
3101
|
+
if (error instanceof InvalidPortableObjectIdError) return "malformed portable ID";
|
|
3102
|
+
if (!isPermanentActivityParseError(error)) throw error;
|
|
3103
|
+
return "malformed document";
|
|
2930
3104
|
}
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
const
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
const message = {
|
|
2937
|
-
type: "outbox",
|
|
2938
|
-
id: crypto.randomUUID(),
|
|
2939
|
-
baseUrl,
|
|
2940
|
-
keys,
|
|
2941
|
-
activity,
|
|
2942
|
-
activityId: activityId.href,
|
|
2943
|
-
activityType,
|
|
2944
|
-
inbox: inbox.href,
|
|
2945
|
-
sharedInbox: false,
|
|
2946
|
-
started,
|
|
2947
|
-
attempt: 0,
|
|
2948
|
-
headers: {},
|
|
2949
|
-
traceContext
|
|
2950
|
-
};
|
|
2951
|
-
try {
|
|
2952
|
-
await outboxQueue.enqueue(message);
|
|
2953
|
-
} catch (error) {
|
|
2954
|
-
logger.error("Failed to enqueue activity {activityId} to forward to {inbox}; it will not be forwarded to the inbox again:\n{error}", {
|
|
2955
|
-
activityId: activityId.href,
|
|
2956
|
-
inbox: inbox.href,
|
|
2957
|
-
error
|
|
2958
|
-
});
|
|
2959
|
-
return;
|
|
2960
|
-
}
|
|
2961
|
-
recordOutboxEnqueue(meterProvider, outboxQueue, message);
|
|
2962
|
-
}));
|
|
3105
|
+
const { result, root } = verification;
|
|
3106
|
+
if (!result.verified) return result.reason.type;
|
|
3107
|
+
const rootId = root?.["@id"];
|
|
3108
|
+
if (typeof rootId !== "string" || getCanonicalPortableId(parseIri(rootId)) !== expectedId) return "the verified document has another ID";
|
|
3109
|
+
return null;
|
|
2963
3110
|
}
|
|
2964
3111
|
//#endregion
|
|
2965
3112
|
//#region src/federation/outgoing-proof.ts
|
|
@@ -5348,17 +5495,39 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5348
5495
|
});
|
|
5349
5496
|
}
|
|
5350
5497
|
const route = this.router.route(portable.path);
|
|
5351
|
-
|
|
5498
|
+
const isActor = route != null && (route.name === "actor" || route.name.startsWith("actorAlias:"));
|
|
5499
|
+
if (route == null || !isActor && !route.name.startsWith("object:")) {
|
|
5352
5500
|
metricState.endpoint = "not_found";
|
|
5353
5501
|
return await onNotFound(request);
|
|
5354
5502
|
}
|
|
5355
5503
|
metricState.routeTemplate = PORTABLE_GATEWAY_ROUTE_PREFIX + route.template;
|
|
5356
|
-
metricState.endpoint = "object";
|
|
5504
|
+
metricState.endpoint = isActor ? "actor" : "object";
|
|
5357
5505
|
span.updateName(`${request.method} ${metricState.routeTemplate}`);
|
|
5358
5506
|
if (!acceptsJsonLd(request)) {
|
|
5359
5507
|
metricState.endpoint = "not_acceptable";
|
|
5360
5508
|
return await onNotAcceptable(request);
|
|
5361
5509
|
}
|
|
5510
|
+
if (isActor) {
|
|
5511
|
+
const identifier = route.name.startsWith("actorAlias:") ? route.name.substring(ACTOR_ALIAS_PREFIX.length) : route.values.identifier;
|
|
5512
|
+
const context = this.#createContext(request, contextData, {
|
|
5513
|
+
invokedFromActorDispatcher: { identifier },
|
|
5514
|
+
portableRequest: portable.portableRequest
|
|
5515
|
+
});
|
|
5516
|
+
const actorDispatcher = this.actorCallbacks?.dispatcher;
|
|
5517
|
+
const authorizePredicate = this.actorCallbacks?.authorizePredicate;
|
|
5518
|
+
return await handlePortableObject(request, {
|
|
5519
|
+
values: { identifier },
|
|
5520
|
+
context,
|
|
5521
|
+
objectDispatcher: actorDispatcher == null ? void 0 : async (ctx) => {
|
|
5522
|
+
const actor = await actorDispatcher(ctx, identifier);
|
|
5523
|
+
return actor instanceof Tombstone ? null : actor;
|
|
5524
|
+
},
|
|
5525
|
+
authorizePredicate: authorizePredicate == null ? void 0 : (ctx) => authorizePredicate(ctx, identifier),
|
|
5526
|
+
canonicalId: portable.canonicalId,
|
|
5527
|
+
onUnauthorized,
|
|
5528
|
+
onNotFound
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5362
5531
|
const typeId = route.name.replace(/^object:/, "");
|
|
5363
5532
|
const callbacks = this.objectCallbacks[typeId];
|
|
5364
5533
|
const cls = this.objectTypeIds[typeId];
|
|
@@ -5457,7 +5626,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5457
5626
|
onNotFound,
|
|
5458
5627
|
portableInbox: {
|
|
5459
5628
|
recipient,
|
|
5460
|
-
forward: async (activity, activityId, activityType) => {
|
|
5629
|
+
forward: async (activity, activityId, activityType, signatures) => {
|
|
5461
5630
|
if (this.portableInboxForwarding.maxTargets < 1) return;
|
|
5462
5631
|
if (this.kv.cas == null) {
|
|
5463
5632
|
if (!this.#portableInboxForwardingWarned) {
|
|
@@ -5480,6 +5649,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5480
5649
|
allowPrivateAddress: this.allowPrivateAddress,
|
|
5481
5650
|
getKeys: () => this.#getPortableGatewayKeyPairs(context, identifier, recipient),
|
|
5482
5651
|
specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
|
|
5652
|
+
signatures,
|
|
5483
5653
|
options: this.portableInboxForwarding,
|
|
5484
5654
|
meterProvider: this.meterProvider,
|
|
5485
5655
|
tracerProvider: this.tracerProvider
|
|
@@ -5721,9 +5891,17 @@ var ContextImpl = class ContextImpl {
|
|
|
5721
5891
|
return new URL(path, this.canonicalOrigin);
|
|
5722
5892
|
}
|
|
5723
5893
|
getActorUri(identifier) {
|
|
5894
|
+
return new URL(this.#getActorPath(identifier), this.canonicalOrigin);
|
|
5895
|
+
}
|
|
5896
|
+
getPortableActorUri(identifier, authority) {
|
|
5897
|
+
const path = this.#getActorPath(identifier);
|
|
5898
|
+
if (authority == null) throw new TypeError("The authority of a portable ID is required outside an FEP-ef61 gateway request.");
|
|
5899
|
+
return buildPortableUri(authority, path);
|
|
5900
|
+
}
|
|
5901
|
+
#getActorPath(identifier) {
|
|
5724
5902
|
const path = this.federation.router.build(`actorAlias:${identifier}`, {}) ?? this.federation.router.build("actor", { identifier });
|
|
5725
5903
|
if (path == null) throw new RouterError("No actor dispatcher registered.");
|
|
5726
|
-
return
|
|
5904
|
+
return path;
|
|
5727
5905
|
}
|
|
5728
5906
|
getObjectUri(cls, values) {
|
|
5729
5907
|
return new URL(this.#getObjectPath(cls, values), this.canonicalOrigin);
|
|
@@ -6366,6 +6544,9 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
6366
6544
|
request: this.request
|
|
6367
6545
|
});
|
|
6368
6546
|
}
|
|
6547
|
+
getPortableActorUri(identifier, authority) {
|
|
6548
|
+
return super.getPortableActorUri(identifier, authority ?? this.portableRequest?.authority);
|
|
6549
|
+
}
|
|
6369
6550
|
getPortableObjectUri(cls, values, authority) {
|
|
6370
6551
|
return super.getPortableObjectUri(cls, values, authority ?? this.portableRequest?.authority);
|
|
6371
6552
|
}
|
|
@@ -6762,4 +6943,4 @@ function getRequestId(request) {
|
|
|
6762
6943
|
return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
6763
6944
|
}
|
|
6764
6945
|
//#endregion
|
|
6765
|
-
export {
|
|
6946
|
+
export { handleNodeInfo as C, autoIdAssigner as E, handleBenchmarkTrigger as S, actorDehydrator as T, coversDelivery as _, OutboxContextImpl as a, PORTABLE_OBJECT_CONTENT_TYPE as b, assertPortableActorActivity as c, handleCustomCollection as d, handleInbox as f, respondWithObjectIfAcceptable as g, respondWithObject as h, KvSpecDeterminer as i, handleActor as l, handleOutbox as m, FederationImpl as n, createFederation as o, handleObject as p, InboxContextImpl as r, handleWebFinger as s, ContextImpl as t, handleCollection as u, forwardPortableInboxActivity as v, handleNodeInfoJrd as w, parseHashlinkGatewayRequest as x, resolvePortableInboxForwardingOptions as y };
|