@fedify/fedify 2.4.0-dev.2030 → 2.4.0-dev.2047
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-BYJ5otJa.mjs → builder-Cue1kVHU.mjs} +12 -11
- 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-BIvUoJxm.mjs → compound-proof-YT4tajyj.mjs} +1 -1
- package/dist/{context-uyRUWoZQ.d.ts → context-BJPHhBmD.d.ts} +100 -2
- package/dist/{context-BeDnDuF3.d.cts → context-Bh_mJfbD.d.cts} +100 -2
- package/dist/{context-CbS35fLr.mjs → context-DG5QeQ8W.mjs} +3 -1
- package/dist/{deno-Vj_9wePq.mjs → deno-Dcj6O4LJ.mjs} +1 -1
- package/dist/{docloader-qUudHZVH.mjs → docloader-BRlNjHlQ.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/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 +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable.test.d.mts +2 -0
- package/dist/federation/portable.test.mjs +430 -0
- 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 +115 -2
- package/dist/{federation-CTnaljmy.cjs → federation-Bdu06LEl.cjs} +324 -26
- package/dist/{http-By8tnmEr.js → http-BmdqHyms.js} +1 -1
- package/dist/{http-B5F1JMh0.cjs → http-CGjvl34X.cjs} +1 -1
- package/dist/{http-D4WPcJUe.mjs → http-CK6kjLnO.mjs} +3 -3
- package/dist/{key-B_Zc5Jbd.mjs → key-CzCAq6D9.mjs} +2 -2
- package/dist/{kv-cache-8K70pjtX.cjs → kv-cache-CvEicbrf.cjs} +1 -1
- package/dist/{kv-cache-BXdT7pJQ.mjs → kv-cache-YATuzCy-.mjs} +1 -1
- package/dist/{kv-cache-BswMKQjC.js → kv-cache-fi_-XZco.js} +1 -1
- package/dist/{ld-B16KUSIQ.mjs → ld-CNWm8mJc.mjs} +3 -3
- package/dist/{metrics-DpQqUAmh.mjs → metrics-CDoBPWjf.mjs} +1 -1
- package/dist/{middleware-BL5HFFVU.mjs → middleware-CF6uz8wa.mjs} +1 -1
- package/dist/{middleware-C7_hqDnl.mjs → middleware-CtFNjIUD.mjs} +326 -28
- package/dist/{middleware-Cxto6Bq2.js → middleware-Drzx-OID.js} +325 -27
- package/dist/{mod-D882_Xsg.d.ts → mod-BofpDIcU.d.ts} +1 -1
- package/dist/{mod-x9W0d0Pk.d.cts → mod-DU8aVzQJ.d.cts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +4 -4
- package/dist/mod.d.ts +4 -4
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/{owner-BIOIhZoM.mjs → owner-B3PgGiJ1.mjs} +2 -2
- package/dist/{owner-D-E0bT2O.js → owner-CMnmv7S9.js} +16 -3
- package/dist/{owner-WUYke16i.cjs → owner-Hurn_ocn.cjs} +16 -3
- package/dist/{proof-BVxJu5vv.mjs → proof-fKycdssC.mjs} +18 -5
- package/dist/{send-Cr5FJ7iu.mjs → send-BqBILxYE.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 +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/proof.test.mjs +208 -1
- package/dist/{temporal-CCf0lZV3.mjs → temporal-SUiLJ0Co.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +99 -1
- package/dist/testing/mod.mjs +2 -2
- package/dist/{testing-hbky2gMN.mjs → testing-By1YYsIK.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,27 @@
|
|
|
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-Dcj6O4LJ.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-CDoBPWjf.mjs";
|
|
6
6
|
import { t as formatAcceptSignature } from "./accept-CPVqki78.mjs";
|
|
7
|
-
import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-
|
|
8
|
-
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-
|
|
9
|
-
import { t as getAuthenticatedDocumentLoader } from "./docloader-
|
|
10
|
-
import { n as kvCache } from "./kv-cache-
|
|
7
|
+
import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-CzCAq6D9.mjs";
|
|
8
|
+
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-CK6kjLnO.mjs";
|
|
9
|
+
import { t as getAuthenticatedDocumentLoader } from "./docloader-BRlNjHlQ.mjs";
|
|
10
|
+
import { n as kvCache } from "./kv-cache-YATuzCy-.mjs";
|
|
11
11
|
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
|
|
12
12
|
import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-DpnpgjyX.mjs";
|
|
13
13
|
import { t as nodeInfoToJson } from "./types-DE_LV1JB.mjs";
|
|
14
|
-
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-
|
|
15
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
16
|
-
import { a as verifyObject, n as hasProofLike, r as signObject } from "./proof-
|
|
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-
|
|
14
|
+
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-CNWm8mJc.mjs";
|
|
15
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-B3PgGiJ1.mjs";
|
|
16
|
+
import { a as verifyObject, n as hasProofLike, o as verifyPortableObjectProof, r as signObject } from "./proof-fKycdssC.mjs";
|
|
17
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-BqBILxYE.mjs";
|
|
18
|
+
import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-Cue1kVHU.mjs";
|
|
19
19
|
import { t as CircuitBreaker } from "./circuit-breaker-C5NqoLBJ.mjs";
|
|
20
20
|
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-
|
|
21
|
+
import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "./compound-proof-YT4tajyj.mjs";
|
|
22
22
|
import { t as KvKeyCache } from "./keycache-BUDHB5rY.mjs";
|
|
23
23
|
import { t as acceptsJsonLd } from "./negotiation-CCocWT_N.mjs";
|
|
24
|
-
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-
|
|
24
|
+
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-SUiLJ0Co.mjs";
|
|
25
25
|
import { t as createExponentialBackoffPolicy } from "./retry-zX5xw5e0.mjs";
|
|
26
26
|
import { t as TaskCodec } from "./codec-C6rTrpIp.mjs";
|
|
27
27
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
@@ -30,8 +30,9 @@ import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey,
|
|
|
30
30
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
31
31
|
import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
|
|
32
32
|
import { uniq } from "es-toolkit";
|
|
33
|
-
import { FetchError, getDocumentLoader, getFe34Origin, haveSameFe34Origin } from "@fedify/vocab-runtime";
|
|
33
|
+
import { FetchError, canonicalizePortableUri, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, haveSameFe34Origin, isGatewayUrl, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
|
|
34
34
|
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";
|
|
35
|
+
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
35
36
|
import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
36
37
|
import { domainToASCII } from "node:url";
|
|
37
38
|
//#region src/compat/transformers.ts
|
|
@@ -512,6 +513,68 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
512
513
|
});
|
|
513
514
|
}
|
|
514
515
|
//#endregion
|
|
516
|
+
//#region src/federation/portable.ts
|
|
517
|
+
/**
|
|
518
|
+
* The media type that FEP-ef61 gateways must use for portable objects.
|
|
519
|
+
*/
|
|
520
|
+
const PORTABLE_OBJECT_CONTENT_TYPE = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"";
|
|
521
|
+
const GATEWAY_OBJECT_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
522
|
+
const BARE_DID_PATTERN = /^did:[a-z0-9]+:[^/?#]+$/i;
|
|
523
|
+
/**
|
|
524
|
+
* Recognizes an FEP-ef61 gateway request for a portable object, e.g.,
|
|
525
|
+
* `GET /.well-known/apgateway/did:key:z6Mk.../notes/123`.
|
|
526
|
+
*
|
|
527
|
+
* The query is not part of the portable ID, so it is ignored here. It is not
|
|
528
|
+
* removed from the request itself, which HTTP Signatures may cover.
|
|
529
|
+
*
|
|
530
|
+
* @param url The request URL.
|
|
531
|
+
* @returns The parsed request, or `null` if the URL is not a gateway request
|
|
532
|
+
* for a portable object, e.g., the gateway discovery endpoint or
|
|
533
|
+
* a hashlink media URL.
|
|
534
|
+
*/
|
|
535
|
+
function parsePortableGatewayRequest(url) {
|
|
536
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
537
|
+
if (!GATEWAY_OBJECT_PATH_PATTERN.test(url.pathname)) return null;
|
|
538
|
+
let id;
|
|
539
|
+
let canonicalId;
|
|
540
|
+
try {
|
|
541
|
+
id = fromCompatibleEf61Id(url.origin + url.pathname);
|
|
542
|
+
if (id == null) return null;
|
|
543
|
+
canonicalId = canonicalizePortableUri(formatIri(id));
|
|
544
|
+
} catch (error) {
|
|
545
|
+
if (error instanceof TypeError) return {
|
|
546
|
+
type: "malformed",
|
|
547
|
+
error
|
|
548
|
+
};
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
551
|
+
const authority = getFe34Origin(id);
|
|
552
|
+
const href = id.href;
|
|
553
|
+
return {
|
|
554
|
+
type: "object",
|
|
555
|
+
portableRequest: Object.freeze({
|
|
556
|
+
authority,
|
|
557
|
+
get id() {
|
|
558
|
+
return new URL(href);
|
|
559
|
+
}
|
|
560
|
+
}),
|
|
561
|
+
canonicalId,
|
|
562
|
+
path: id.pathname
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Builds a portable ID from a DID authority and an object path.
|
|
567
|
+
* @param authority The bare DID, e.g., `did:key:z6Mk...`.
|
|
568
|
+
* @param path The object path, e.g., `/notes/123`.
|
|
569
|
+
* @returns The portable ID.
|
|
570
|
+
* @throws {TypeError} If the authority is not a bare DID.
|
|
571
|
+
*/
|
|
572
|
+
function buildPortableUri(authority, path) {
|
|
573
|
+
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.");
|
|
574
|
+
getFe34Origin(authority);
|
|
575
|
+
return parseIri(`ap+ef61://${authority}${path}`);
|
|
576
|
+
}
|
|
577
|
+
//#endregion
|
|
515
578
|
//#region src/federation/handler.ts
|
|
516
579
|
const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
|
|
517
580
|
const INBOX_COMPOUND_PROOF_LIMITS = {
|
|
@@ -607,6 +670,102 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
607
670
|
Vary: "Accept"
|
|
608
671
|
} });
|
|
609
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Handles an FEP-ef61 gateway request for a portable object through an object
|
|
675
|
+
* dispatcher. The object is served only if its ID canonically matches the
|
|
676
|
+
* requested portable ID and it satisfies the FEP-ef61 proof policy.
|
|
677
|
+
* @template TContextData The context data to pass to the context.
|
|
678
|
+
* @param request The HTTP request.
|
|
679
|
+
* @param parameters The parameters for handling the portable object.
|
|
680
|
+
* @returns A promise that resolves to an HTTP response.
|
|
681
|
+
*/
|
|
682
|
+
async function handlePortableObject(request, { values, context, objectDispatcher, authorizePredicate, canonicalId, onNotFound, onUnauthorized }) {
|
|
683
|
+
const logger = getLogger([
|
|
684
|
+
"fedify",
|
|
685
|
+
"federation",
|
|
686
|
+
"object"
|
|
687
|
+
]);
|
|
688
|
+
if (objectDispatcher == null) return await onNotFound(request);
|
|
689
|
+
const object = await objectDispatcher(context, values);
|
|
690
|
+
if (object == null) return await onNotFound(request);
|
|
691
|
+
if (!isRequestedPortableObject(object, canonicalId)) {
|
|
692
|
+
logger.debug("The object {objectId} does not match the requested portable object {portableId}.", {
|
|
693
|
+
objectId: object.id?.href,
|
|
694
|
+
portableId: canonicalId
|
|
695
|
+
});
|
|
696
|
+
return await onNotFound(request);
|
|
697
|
+
}
|
|
698
|
+
if (authorizePredicate != null) {
|
|
699
|
+
if (!await authorizePredicate(context, values)) return await onUnauthorized(request);
|
|
700
|
+
}
|
|
701
|
+
let jsonLd;
|
|
702
|
+
let servedId;
|
|
703
|
+
let result;
|
|
704
|
+
try {
|
|
705
|
+
jsonLd = await object.toJsonLd(context);
|
|
706
|
+
servedId = await getJsonLdRootId(jsonLd, context.contextLoader);
|
|
707
|
+
result = await verifyPortableObjectProof(jsonLd, {
|
|
708
|
+
contextLoader: context.contextLoader,
|
|
709
|
+
documentLoader: context.documentLoader,
|
|
710
|
+
tracerProvider: context.tracerProvider,
|
|
711
|
+
meterProvider: context.meterProvider
|
|
712
|
+
});
|
|
713
|
+
} catch (error) {
|
|
714
|
+
logger.error("Failed to verify the Object Integrity Proofs of the portable object {portableId}:\n{error}", {
|
|
715
|
+
portableId: canonicalId,
|
|
716
|
+
error
|
|
717
|
+
});
|
|
718
|
+
return portableObjectInternalServerError(request);
|
|
719
|
+
}
|
|
720
|
+
if (servedId == null || !isRequestedPortableId(servedId, canonicalId)) {
|
|
721
|
+
logger.debug("The serialized object does not match the requested portable object {portableId}.", { portableId: canonicalId });
|
|
722
|
+
return await onNotFound(request);
|
|
723
|
+
}
|
|
724
|
+
if (!result.verified && result.reason.type !== "unsecuredCollection" && result.reason.type !== "unsupportedObjectType") {
|
|
725
|
+
logger.error("Refusing to serve the portable object {portableId}, as it does not satisfy the FEP-ef61 proof policy: {reason}. Portable actors, activities, and objects need an Object Integrity Proof made with a key of the DID in their ID.", {
|
|
726
|
+
portableId: canonicalId,
|
|
727
|
+
reason: result.reason.type
|
|
728
|
+
});
|
|
729
|
+
return portableObjectInternalServerError(request);
|
|
730
|
+
}
|
|
731
|
+
return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), { headers: {
|
|
732
|
+
"Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
|
|
733
|
+
Vary: "Accept"
|
|
734
|
+
} });
|
|
735
|
+
}
|
|
736
|
+
function isRequestedPortableObject(object, canonicalId) {
|
|
737
|
+
if (object.id == null) return false;
|
|
738
|
+
if (object.id.protocol !== "ap:" && object.id.protocol !== "ap+ef61:") return false;
|
|
739
|
+
try {
|
|
740
|
+
return canonicalizePortableUri(formatIri(object.id)) === canonicalId;
|
|
741
|
+
} catch (error) {
|
|
742
|
+
if (error instanceof TypeError) return false;
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
async function getJsonLdRootId(jsonLd, contextLoader) {
|
|
747
|
+
const expanded = await jsonld.expand(jsonLd, {
|
|
748
|
+
documentLoader: getNormalizationContextLoader(contextLoader),
|
|
749
|
+
keepFreeFloatingNodes: true
|
|
750
|
+
});
|
|
751
|
+
if (expanded.length !== 1) return null;
|
|
752
|
+
const id = expanded[0]["@id"];
|
|
753
|
+
return typeof id === "string" ? id : null;
|
|
754
|
+
}
|
|
755
|
+
function isRequestedPortableId(id, canonicalId) {
|
|
756
|
+
try {
|
|
757
|
+
return canonicalizePortableUri(id) === canonicalId;
|
|
758
|
+
} catch (error) {
|
|
759
|
+
if (error instanceof TypeError) return false;
|
|
760
|
+
throw error;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
function portableObjectInternalServerError(request) {
|
|
764
|
+
return new Response(request.method === "HEAD" ? null : "Internal server error.", {
|
|
765
|
+
status: 500,
|
|
766
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
767
|
+
});
|
|
768
|
+
}
|
|
610
769
|
const BUILT_IN_COLLECTION_METRIC_KINDS = /* @__PURE__ */ new Set([
|
|
611
770
|
"inbox",
|
|
612
771
|
"outbox",
|
|
@@ -644,13 +803,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
|
644
803
|
* @param parameters The parameters for handling the collection.
|
|
645
804
|
* @returns A promise that resolves to an HTTP response.
|
|
646
805
|
*/
|
|
647
|
-
async function handleCollection(request, { name: name$
|
|
648
|
-
const spanName = name$
|
|
806
|
+
async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
807
|
+
const spanName = name$2.trim().replace(/\s+/g, "_");
|
|
649
808
|
tracerProvider = tracerProvider ?? trace.getTracerProvider();
|
|
650
809
|
const tracer = tracerProvider.getTracer(name, version);
|
|
651
810
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
652
811
|
const metricBase = {
|
|
653
|
-
kind: getCollectionMetricKind(name$
|
|
812
|
+
kind: getCollectionMetricKind(name$2),
|
|
654
813
|
page: cursor != null,
|
|
655
814
|
dispatcher: "built_in"
|
|
656
815
|
};
|
|
@@ -691,7 +850,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
691
850
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
692
851
|
return await onNotFound(request);
|
|
693
852
|
}
|
|
694
|
-
const items = filterCollectionItems(page.items, name$
|
|
853
|
+
const items = filterCollectionItems(page.items, name$2, filterPredicate);
|
|
695
854
|
itemCount = items.length;
|
|
696
855
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
697
856
|
return items;
|
|
@@ -731,7 +890,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
731
890
|
} else {
|
|
732
891
|
const uri = new URL(baseUri);
|
|
733
892
|
uri.searchParams.set("cursor", cursor);
|
|
734
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
893
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
|
|
735
894
|
kind: SpanKind.SERVER,
|
|
736
895
|
attributes: {
|
|
737
896
|
"activitypub.collection.id": uri.href,
|
|
@@ -747,7 +906,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
747
906
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
748
907
|
return await onNotFound(request);
|
|
749
908
|
}
|
|
750
|
-
const items = filterCollectionItems(page.items, name$
|
|
909
|
+
const items = filterCollectionItems(page.items, name$2, filterPredicate);
|
|
751
910
|
itemCount = items.length;
|
|
752
911
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
753
912
|
return {
|
|
@@ -1840,8 +1999,8 @@ var CustomCollectionHandler = class {
|
|
|
1840
1999
|
* @param CollectionPage The CollectionPage constructor.
|
|
1841
2000
|
* @param filterPredicate Optional filter predicate for items.
|
|
1842
2001
|
*/
|
|
1843
|
-
constructor(name$
|
|
1844
|
-
this.name = name$
|
|
2002
|
+
constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
|
|
2003
|
+
this.name = name$1;
|
|
1845
2004
|
this.values = values;
|
|
1846
2005
|
this.context = context;
|
|
1847
2006
|
this.callbacks = callbacks;
|
|
@@ -2640,6 +2799,34 @@ function getWebFingerSubjectAndAliases({ resourceUrl, actorUri, preferredUsernam
|
|
|
2640
2799
|
};
|
|
2641
2800
|
}
|
|
2642
2801
|
/**
|
|
2802
|
+
* Computes the subject and aliases for an FEP-ef61 portable actor, whose
|
|
2803
|
+
* WebFinger address takes its domain from the first gateway of the actor
|
|
2804
|
+
* rather than from the actor ID. The canonical `ap:` ID is not emitted,
|
|
2805
|
+
* since it is not a valid URI for software that does not support FEP-ef61;
|
|
2806
|
+
* the compatible identifier stands for it.
|
|
2807
|
+
*/
|
|
2808
|
+
function getPortableWebFingerSubjectAndAliases({ resourceUrl, compatibleId, preferredUsername, acctUsername, gatewayHost }) {
|
|
2809
|
+
const aliases = [];
|
|
2810
|
+
const add = (alias) => {
|
|
2811
|
+
if (alias !== subject && !aliases.includes(alias)) aliases.push(alias);
|
|
2812
|
+
};
|
|
2813
|
+
let subject;
|
|
2814
|
+
if (resourceUrl.protocol === "acct:") {
|
|
2815
|
+
const username = preferredUsername ?? acctUsername;
|
|
2816
|
+
subject = username == null ? resourceUrl.href : `acct:${username}@${gatewayHost}`;
|
|
2817
|
+
add(compatibleId.href);
|
|
2818
|
+
add(resourceUrl.href);
|
|
2819
|
+
} else {
|
|
2820
|
+
subject = preferredUsername == null ? compatibleId.href : `acct:${preferredUsername}@${gatewayHost}`;
|
|
2821
|
+
add(compatibleId.href);
|
|
2822
|
+
if (resourceUrl.protocol === "http:" || resourceUrl.protocol === "https:") add(resourceUrl.href);
|
|
2823
|
+
}
|
|
2824
|
+
return {
|
|
2825
|
+
subject,
|
|
2826
|
+
aliases
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2643
2830
|
* Handles a WebFinger request. You would not typically call this function
|
|
2644
2831
|
* directly, but instead use {@link Federation.fetch} method.
|
|
2645
2832
|
* @param request The WebFinger request to handle.
|
|
@@ -2724,7 +2911,7 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
2724
2911
|
span?.setAttribute("webfinger.resource", resource);
|
|
2725
2912
|
let resourceUrl;
|
|
2726
2913
|
try {
|
|
2727
|
-
resourceUrl =
|
|
2914
|
+
resourceUrl = parseIri(resource);
|
|
2728
2915
|
} catch (e) {
|
|
2729
2916
|
if (e instanceof TypeError) return new Response("Invalid resource URL.", { status: 400 });
|
|
2730
2917
|
throw e;
|
|
@@ -2773,7 +2960,29 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
2773
2960
|
status: 410,
|
|
2774
2961
|
headers: { "Access-Control-Allow-Origin": "*" }
|
|
2775
2962
|
});
|
|
2776
|
-
|
|
2963
|
+
let actorUri = context.getActorUri(identifier);
|
|
2964
|
+
let portable;
|
|
2965
|
+
if (actor.id != null && (actor.id.protocol === "ap:" || actor.id.protocol === "ap+ef61:")) {
|
|
2966
|
+
const gateway = actor.gateway;
|
|
2967
|
+
let compatibleId;
|
|
2968
|
+
if (gateway != null && isGatewayUrl(gateway)) try {
|
|
2969
|
+
compatibleId = toCompatibleEf61Id(actor.id, gateway);
|
|
2970
|
+
} catch (error) {
|
|
2971
|
+
if (!(error instanceof TypeError)) throw error;
|
|
2972
|
+
}
|
|
2973
|
+
if (gateway == null || compatibleId == null) {
|
|
2974
|
+
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.", {
|
|
2975
|
+
actorId: formatIri(actor.id),
|
|
2976
|
+
identifier
|
|
2977
|
+
});
|
|
2978
|
+
return await onNotFound(request);
|
|
2979
|
+
}
|
|
2980
|
+
actorUri = compatibleId;
|
|
2981
|
+
portable = {
|
|
2982
|
+
compatibleId,
|
|
2983
|
+
gatewayHost: gateway.host
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2777
2986
|
const links = [{
|
|
2778
2987
|
rel: "self",
|
|
2779
2988
|
href: actorUri.href,
|
|
@@ -2800,13 +3009,19 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
|
|
|
2800
3009
|
const customLinks = await webFingerLinksDispatcher(context, resourceUrl);
|
|
2801
3010
|
if (customLinks != null) for (const link of customLinks) links.push(link);
|
|
2802
3011
|
}
|
|
2803
|
-
const { subject, aliases } = getWebFingerSubjectAndAliases({
|
|
3012
|
+
const { subject, aliases } = portable == null ? getWebFingerSubjectAndAliases({
|
|
2804
3013
|
resourceUrl,
|
|
2805
3014
|
actorUri,
|
|
2806
3015
|
preferredUsername: actor.preferredUsername,
|
|
2807
3016
|
acctUsername,
|
|
2808
3017
|
host,
|
|
2809
3018
|
contextHost: context.url.host
|
|
3019
|
+
}) : getPortableWebFingerSubjectAndAliases({
|
|
3020
|
+
resourceUrl,
|
|
3021
|
+
compatibleId: portable.compatibleId,
|
|
3022
|
+
preferredUsername: actor.preferredUsername,
|
|
3023
|
+
acctUsername,
|
|
3024
|
+
gatewayHost: portable.gatewayHost
|
|
2810
3025
|
});
|
|
2811
3026
|
return new Response(JSON.stringify({
|
|
2812
3027
|
subject,
|
|
@@ -4025,7 +4240,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4025
4240
|
...ctxOptions,
|
|
4026
4241
|
request,
|
|
4027
4242
|
invokedFromActorDispatcher: opts.invokedFromActorDispatcher,
|
|
4028
|
-
invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher
|
|
4243
|
+
invokedFromObjectDispatcher: opts.invokedFromObjectDispatcher,
|
|
4244
|
+
portableRequest: opts.portableRequest
|
|
4029
4245
|
});
|
|
4030
4246
|
}
|
|
4031
4247
|
#getLoaderOptions(origin) {
|
|
@@ -4276,6 +4492,24 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4276
4492
|
const url = new URL(request.url);
|
|
4277
4493
|
const route = this.router.route(url.pathname);
|
|
4278
4494
|
if (route == null) {
|
|
4495
|
+
const portable = request.method === "GET" || request.method === "HEAD" ? parsePortableGatewayRequest(url) : null;
|
|
4496
|
+
if (portable != null) {
|
|
4497
|
+
const response = await this.#fetchPortableObject(request, portable, {
|
|
4498
|
+
onNotFound,
|
|
4499
|
+
onNotAcceptable,
|
|
4500
|
+
onUnauthorized,
|
|
4501
|
+
contextData,
|
|
4502
|
+
span,
|
|
4503
|
+
metricState
|
|
4504
|
+
});
|
|
4505
|
+
if (request.method !== "HEAD" || response.body == null) return response;
|
|
4506
|
+
await response.body.cancel();
|
|
4507
|
+
return new Response(null, {
|
|
4508
|
+
status: response.status,
|
|
4509
|
+
statusText: response.statusText,
|
|
4510
|
+
headers: response.headers
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4279
4513
|
metricState.endpoint = "not_found";
|
|
4280
4514
|
return await onNotFound(request);
|
|
4281
4515
|
}
|
|
@@ -4551,7 +4785,56 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4551
4785
|
}
|
|
4552
4786
|
}
|
|
4553
4787
|
}
|
|
4788
|
+
async #fetchPortableObject(request, portable, { onNotFound, onNotAcceptable, onUnauthorized, contextData, span, metricState }) {
|
|
4789
|
+
if (portable.type === "malformed") {
|
|
4790
|
+
metricState.endpoint = "not_found";
|
|
4791
|
+
getLogger([
|
|
4792
|
+
"fedify",
|
|
4793
|
+
"federation",
|
|
4794
|
+
"object"
|
|
4795
|
+
]).debug("Malformed FEP-ef61 gateway request {url}: {error}", {
|
|
4796
|
+
url: request.url,
|
|
4797
|
+
error: portable.error
|
|
4798
|
+
});
|
|
4799
|
+
return new Response(request.method === "HEAD" ? null : "Malformed portable object ID.", {
|
|
4800
|
+
status: 400,
|
|
4801
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" }
|
|
4802
|
+
});
|
|
4803
|
+
}
|
|
4804
|
+
const route = this.router.route(portable.path);
|
|
4805
|
+
if (route == null || !route.name.startsWith("object:")) {
|
|
4806
|
+
metricState.endpoint = "not_found";
|
|
4807
|
+
return await onNotFound(request);
|
|
4808
|
+
}
|
|
4809
|
+
metricState.routeTemplate = PORTABLE_GATEWAY_ROUTE_PREFIX + route.template;
|
|
4810
|
+
metricState.endpoint = "object";
|
|
4811
|
+
span.updateName(`${request.method} ${metricState.routeTemplate}`);
|
|
4812
|
+
if (!acceptsJsonLd(request)) {
|
|
4813
|
+
metricState.endpoint = "not_acceptable";
|
|
4814
|
+
return await onNotAcceptable(request);
|
|
4815
|
+
}
|
|
4816
|
+
const typeId = route.name.replace(/^object:/, "");
|
|
4817
|
+
const callbacks = this.objectCallbacks[typeId];
|
|
4818
|
+
const cls = this.objectTypeIds[typeId];
|
|
4819
|
+
const context = this.#createContext(request, contextData, {
|
|
4820
|
+
invokedFromObjectDispatcher: {
|
|
4821
|
+
cls,
|
|
4822
|
+
values: route.values
|
|
4823
|
+
},
|
|
4824
|
+
portableRequest: portable.portableRequest
|
|
4825
|
+
});
|
|
4826
|
+
return await handlePortableObject(request, {
|
|
4827
|
+
values: route.values,
|
|
4828
|
+
context,
|
|
4829
|
+
objectDispatcher: callbacks?.dispatcher,
|
|
4830
|
+
authorizePredicate: callbacks?.authorizePredicate,
|
|
4831
|
+
canonicalId: portable.canonicalId,
|
|
4832
|
+
onUnauthorized,
|
|
4833
|
+
onNotFound
|
|
4834
|
+
});
|
|
4835
|
+
}
|
|
4554
4836
|
};
|
|
4837
|
+
const PORTABLE_GATEWAY_ROUTE_PREFIX = "/.well-known/apgateway/{did}";
|
|
4555
4838
|
function getEndpointCategory(routeName) {
|
|
4556
4839
|
if (routeName.startsWith("object:")) return "object";
|
|
4557
4840
|
if (routeName.startsWith("collection:") || routeName.startsWith("orderedCollection:")) return "collection";
|
|
@@ -4686,12 +4969,20 @@ var ContextImpl = class ContextImpl {
|
|
|
4686
4969
|
return new URL(path, this.canonicalOrigin);
|
|
4687
4970
|
}
|
|
4688
4971
|
getObjectUri(cls, values) {
|
|
4972
|
+
return new URL(this.#getObjectPath(cls, values), this.canonicalOrigin);
|
|
4973
|
+
}
|
|
4974
|
+
getPortableObjectUri(cls, values, authority) {
|
|
4975
|
+
const path = this.#getObjectPath(cls, values);
|
|
4976
|
+
if (authority == null) throw new TypeError("The authority of a portable ID is required outside an FEP-ef61 gateway request.");
|
|
4977
|
+
return buildPortableUri(authority, path);
|
|
4978
|
+
}
|
|
4979
|
+
#getObjectPath(cls, values) {
|
|
4689
4980
|
const callbacks = this.federation.objectCallbacks[cls.typeId.href];
|
|
4690
4981
|
if (callbacks == null) throw new RouterError("No object dispatcher registered.");
|
|
4691
4982
|
for (const param of callbacks.parameters) if (!(param in values)) throw new TypeError(`Missing parameter: ${param}`);
|
|
4692
4983
|
const path = this.federation.router.build(`object:${cls.typeId.href}`, values);
|
|
4693
4984
|
if (path == null) throw new RouterError("No object dispatcher registered.");
|
|
4694
|
-
return
|
|
4985
|
+
return path;
|
|
4695
4986
|
}
|
|
4696
4987
|
getOutboxUri(identifier) {
|
|
4697
4988
|
const path = this.federation.router.build("outbox", { identifier });
|
|
@@ -4920,6 +5211,7 @@ var ContextImpl = class ContextImpl {
|
|
|
4920
5211
|
userAgent: options.userAgent ?? this.federation.userAgent,
|
|
4921
5212
|
tracerProvider: options.tracerProvider ?? this.tracerProvider,
|
|
4922
5213
|
meterProvider: options.meterProvider ?? this.meterProvider,
|
|
5214
|
+
verifyPortableObject: options.verifyPortableObject ?? verifyPortableObjectProof,
|
|
4923
5215
|
allowPrivateAddress: this.federation.allowPrivateAddress
|
|
4924
5216
|
});
|
|
4925
5217
|
}
|
|
@@ -5266,12 +5558,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
5266
5558
|
#invokedFromObjectDispatcher;
|
|
5267
5559
|
request;
|
|
5268
5560
|
url = void 0;
|
|
5561
|
+
portableRequest;
|
|
5269
5562
|
constructor(options) {
|
|
5270
5563
|
super(options);
|
|
5271
5564
|
this.#invokedFromActorDispatcher = options.invokedFromActorDispatcher;
|
|
5272
5565
|
this.#invokedFromObjectDispatcher = options.invokedFromObjectDispatcher;
|
|
5273
5566
|
this.request = options.request;
|
|
5274
5567
|
this.url = options.url;
|
|
5568
|
+
this.portableRequest = options.portableRequest;
|
|
5275
5569
|
}
|
|
5276
5570
|
clone(data) {
|
|
5277
5571
|
return new RequestContextImpl({
|
|
@@ -5283,9 +5577,13 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
|
|
|
5283
5577
|
invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher,
|
|
5284
5578
|
invokedFromActorDispatcher: this.#invokedFromActorDispatcher,
|
|
5285
5579
|
invokedFromObjectDispatcher: this.#invokedFromObjectDispatcher,
|
|
5580
|
+
portableRequest: this.portableRequest,
|
|
5286
5581
|
request: this.request
|
|
5287
5582
|
});
|
|
5288
5583
|
}
|
|
5584
|
+
getPortableObjectUri(cls, values, authority) {
|
|
5585
|
+
return super.getPortableObjectUri(cls, values, authority ?? this.portableRequest?.authority);
|
|
5586
|
+
}
|
|
5289
5587
|
async getActor(identifier, options) {
|
|
5290
5588
|
if (this.federation.actorCallbacks == null || this.federation.actorCallbacks.dispatcher == null) throw new Error("No actor dispatcher registered.");
|
|
5291
5589
|
if (this.#invokedFromActorDispatcher != null) getLogger([
|
|
@@ -5676,4 +5974,4 @@ function getRequestId(request) {
|
|
|
5676
5974
|
return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
5677
5975
|
}
|
|
5678
5976
|
//#endregion
|
|
5679
|
-
export {
|
|
5977
|
+
export { handleBenchmarkTrigger as _, OutboxContextImpl as a, actorDehydrator as b, handleActor as c, handleInbox as d, handleObject as f, PORTABLE_OBJECT_CONTENT_TYPE as g, respondWithObjectIfAcceptable as h, KvSpecDeterminer as i, handleCollection as l, respondWithObject as m, FederationImpl as n, createFederation as o, handleOutbox as p, InboxContextImpl as r, handleWebFinger as s, ContextImpl as t, handleCustomCollection as u, handleNodeInfo as v, autoIdAssigner as x, handleNodeInfoJrd as y };
|