@fedify/fedify 2.4.0-dev.2077 → 2.4.0-dev.2093
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-i3jxVe9m.mjs → builder-Ld4Y9ymR.mjs} +34 -3
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-Cayy-ECj.mjs → compound-proof-CTx_lewQ.mjs} +191 -13
- package/dist/{deno-Djkzd-G_.mjs → deno-8t8k_6Yv.mjs} +1 -1
- package/dist/{docloader-dA0RswUV.mjs → docloader-DJKcsAiF.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +254 -5
- package/dist/federation/portable-gateway-keys.test.mjs +72 -11
- package/dist/federation/portable-inbox.test.mjs +513 -6
- package/dist/federation/portable.test.mjs +182 -4
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +89 -1
- package/dist/{federation-CFd0Jhhv.cjs → federation-aSHO04ob.cjs} +472 -74
- package/dist/{http-CCJ97Jql.mjs → http-B2ftY0Ha.mjs} +4 -4
- package/dist/{http-Dvy_IwRa.cjs → http-DSaSAN7X.cjs} +1609 -1457
- package/dist/{http-aqh3yFdM.js → http-DyT7DP1Y.js} +1518 -1390
- package/dist/{key-IxTTC8Yf.mjs → key-Dwm4VswT.mjs} +39 -99
- package/dist/{kv-cache-BP69NV0P.cjs → kv-cache-BSJAlP4C.cjs} +1 -1
- package/dist/{kv-cache-Bwwbh-xR.mjs → kv-cache-D0yYukqk.mjs} +1 -1
- package/dist/{kv-cache-Y6SDRSX6.js → kv-cache-DmroAz3M.js} +1 -1
- package/dist/{ld-l0B9nREb.mjs → ld-BYZOoDzZ.mjs} +3 -3
- package/dist/{metrics-D1p6hv4z.mjs → metrics-B7YwKTFr.mjs} +1 -1
- package/dist/{middleware-BDjCUHMS.mjs → middleware-BNeqKSsL.mjs} +1 -1
- package/dist/{middleware-SSfryVzY.js → middleware-Blq0882d.js} +480 -82
- package/dist/{middleware-BMgzqBtr.mjs → middleware-E_0IH72m.mjs} +278 -80
- package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
- package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-DGbYmXqU.mjs → owner-Cgx2Dc7I.mjs} +5 -4
- package/dist/{owner-jH1z_YfD.js → owner-DAGSC5gX.js} +2 -2
- package/dist/{owner-BpOxL5PG.cjs → owner-szIZl4qI.cjs} +2 -2
- package/dist/{portable-key-CCwJGSsU.mjs → portable-key-Cn1WJSN1.mjs} +60 -41
- package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
- package/dist/{proof-CVL7JnZd.mjs → proof-DVXC6nYz.mjs} +59 -18
- package/dist/{send-Ejy1jOz9.mjs → send-D-hOvYKy.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +160 -3
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +3 -3
- package/dist/sig/mod.d.cts +1 -1
- package/dist/sig/mod.d.ts +1 -1
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +69 -5
- package/dist/sig/portable-key.test.mjs +176 -10
- package/dist/sig/proof.test.mjs +107 -4
- package/dist/{sig-Dpy1B1Ad.js → sig-B5Ell7pO.js} +19 -3
- package/dist/{sig-xoqy3ATq.cjs → sig-CXOKN4Br.cjs} +19 -3
- package/dist/{temporal-DNJ1cNpr.mjs → temporal-CqpKGEPk.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
|
@@ -1,105 +1,13 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
|
|
5
|
+
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-B7YwKTFr.mjs";
|
|
6
|
+
import { a as isCompatibleKeyId, o as isPortableActorDocument, s as isPortableId } from "./portable-key-id-Bva5iT3W.mjs";
|
|
6
7
|
import { getLogger } from "@logtape/logtape";
|
|
7
8
|
import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
8
9
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
|
-
import { FetchError,
|
|
10
|
-
//#region src/sig/portable-key-id.ts
|
|
11
|
-
const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
|
|
12
|
-
/**
|
|
13
|
-
* Checks whether the URL is an `ap:` or `ap+ef61:` URI.
|
|
14
|
-
* @internal
|
|
15
|
-
*/
|
|
16
|
-
function isPortableUri(url) {
|
|
17
|
-
return url.protocol === "ap:" || url.protocol === "ap+ef61:";
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Checks whether an activity is performed by an FEP-ef61 portable actor,
|
|
21
|
-
* i.e., any of its actors has an `ap:` or `ap+ef61:` ID.
|
|
22
|
-
* @internal
|
|
23
|
-
*/
|
|
24
|
-
function hasPortableActor(activity) {
|
|
25
|
-
return activity.actorIds.some(isPortableUri);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Checks whether a key ID looks like an [FEP-ef61] compatible identifier,
|
|
29
|
-
* i.e., an HTTP(S) URL under a gateway's `/.well-known/apgateway/did:` path.
|
|
30
|
-
* Such a key ID names a key that a gateway holds for a portable actor, so it
|
|
31
|
-
* is resolved only as a gateway key, and never cached in a key cache.
|
|
32
|
-
*
|
|
33
|
-
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
34
|
-
* @internal
|
|
35
|
-
*/
|
|
36
|
-
function isCompatibleKeyId(keyId) {
|
|
37
|
-
return (keyId.protocol === "http:" || keyId.protocol === "https:") && COMPATIBLE_KEY_ID_PATH_PATTERN.test(keyId.pathname);
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Gets the canonical portable ID of an actor ID that is either an `ap:` or
|
|
41
|
-
* `ap+ef61:` URI or a compatible identifier.
|
|
42
|
-
* @returns The canonical portable ID, or `null` if the ID is neither, or is
|
|
43
|
-
* malformed.
|
|
44
|
-
* @internal
|
|
45
|
-
*/
|
|
46
|
-
function getCanonicalPortableId(id) {
|
|
47
|
-
try {
|
|
48
|
-
if (isPortableUri(id)) return canonicalizePortableUri(formatIri(id));
|
|
49
|
-
const portable = fromCompatibleEf61Id(id);
|
|
50
|
-
if (portable == null) return null;
|
|
51
|
-
return canonicalizePortableUri(formatIri(portable));
|
|
52
|
-
} catch (error) {
|
|
53
|
-
if (error instanceof TypeError) return null;
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Builds the base of the key IDs of a gateway's keys for a portable actor:
|
|
59
|
-
* the actor's compatible identifier on the gateway.
|
|
60
|
-
* @param actorId The portable actor's ID, either an `ap:` or `ap+ef61:` URI
|
|
61
|
-
* or a compatible identifier. It must not have a fragment.
|
|
62
|
-
* @param gateway The gateway's origin, e.g., `https://example.com`.
|
|
63
|
-
* @returns The compatible identifier without query and fragment.
|
|
64
|
-
* @throws {TypeError} If the actor ID is not a portable ID, or has
|
|
65
|
-
* a fragment.
|
|
66
|
-
* @internal
|
|
67
|
-
*/
|
|
68
|
-
function getGatewayKeyBase(actorId, gateway) {
|
|
69
|
-
let portable;
|
|
70
|
-
if (isPortableUri(actorId)) portable = actorId;
|
|
71
|
-
else try {
|
|
72
|
-
portable = fromCompatibleEf61Id(actorId);
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (!(error instanceof TypeError)) throw error;
|
|
75
|
-
portable = null;
|
|
76
|
-
}
|
|
77
|
-
if (portable == null) throw new TypeError(`The portable actor ID ${actorId.href} must be an ap: or ap+ef61: URI, or an FEP-ef61 compatible identifier.`);
|
|
78
|
-
if (portable.hash !== "") throw new TypeError(`The portable actor ID ${actorId.href} must not have a fragment.`);
|
|
79
|
-
const base = toCompatibleEf61Id(portable, gateway);
|
|
80
|
-
base.search = "";
|
|
81
|
-
base.hash = "";
|
|
82
|
-
return base;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Tells whether a parsed document at a compatible key ID is a portable actor,
|
|
86
|
-
* which makes the key a gateway key that only
|
|
87
|
-
* {@link verifyPortableGatewayKeyDocument} can vouch for.
|
|
88
|
-
* @internal
|
|
89
|
-
*/
|
|
90
|
-
function isPortableActorDocument(object) {
|
|
91
|
-
return isActor(object) && object.id != null && isPortableUri(object.id);
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Compares two public keys by their key material.
|
|
95
|
-
* @internal
|
|
96
|
-
*/
|
|
97
|
-
async function isSamePublicKey(a, b) {
|
|
98
|
-
if (a.algorithm.name !== b.algorithm.name) return false;
|
|
99
|
-
const [x, y] = await Promise.all([crypto.subtle.exportKey("jwk", a), crypto.subtle.exportKey("jwk", b)]);
|
|
100
|
-
return x.kty === y.kty && x.crv === y.crv && x.n === y.n && x.e === y.e && x.x === y.x && x.y === y.y;
|
|
101
|
-
}
|
|
102
|
-
//#endregion
|
|
10
|
+
import { FetchError, getDocumentLoader, parseDidKeyVerificationMethod } from "@fedify/vocab-runtime";
|
|
103
11
|
//#region src/sig/key.ts
|
|
104
12
|
/**
|
|
105
13
|
* Checks if the given key is valid and supported. No-op if the key is valid,
|
|
@@ -200,6 +108,10 @@ async function fetchActorDocument(actorId, options = {}) {
|
|
|
200
108
|
"sig",
|
|
201
109
|
"key"
|
|
202
110
|
]);
|
|
111
|
+
if (isPortableId(actorId)) {
|
|
112
|
+
logger.debug("The actor {actorId} is a portable actor, which is not authenticated by the web origin that serves it.", { actorId: actorId.href });
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
203
115
|
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
204
116
|
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
205
117
|
const { tracerProvider } = options;
|
|
@@ -233,6 +145,13 @@ async function fetchActorDocument(actorId, options = {}) {
|
|
|
233
145
|
return null;
|
|
234
146
|
}
|
|
235
147
|
if (!isActor(object)) return null;
|
|
148
|
+
if (object.id != null && isPortableId(object.id)) {
|
|
149
|
+
logger.debug("The document served at {documentUrl} claims to be the portable actor {actorId}, which is not authenticated by the web origin that serves it.", {
|
|
150
|
+
documentUrl: documentUrl.href,
|
|
151
|
+
actorId: object.id?.href
|
|
152
|
+
});
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
236
155
|
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
237
156
|
logger.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as that actor's document.", {
|
|
238
157
|
documentUrl: documentUrl.href,
|
|
@@ -567,7 +486,7 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
567
486
|
let ownerDocument = null;
|
|
568
487
|
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
569
488
|
else if (isActor(object)) {
|
|
570
|
-
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
489
|
+
if (object.id == null || isPortableId(object.id) || object.id.origin !== documentUrl.origin) {
|
|
571
490
|
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
572
491
|
keyId,
|
|
573
492
|
documentUrl: documentUrl.href,
|
|
@@ -643,6 +562,18 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
643
562
|
}
|
|
644
563
|
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
645
564
|
if (!(keyIdBoundByCaller && claimedOwnerId != null && claimedOwnerId.href === stripFragment(cacheKey)?.href)) {
|
|
565
|
+
if (claimedOwnerId != null && isPortableId(claimedOwnerId)) {
|
|
566
|
+
logger.debug("Failed to verify; key {keyId} claims the portable actor {claimedOwnerId} as its owner, but is not its gateway key.", {
|
|
567
|
+
keyId,
|
|
568
|
+
claimedOwnerId: claimedOwnerId.href
|
|
569
|
+
});
|
|
570
|
+
await keyCache?.set(cacheKey, null);
|
|
571
|
+
await clearFetchErrorMetadata(cacheKey, keyCache);
|
|
572
|
+
return {
|
|
573
|
+
key: null,
|
|
574
|
+
cached: false
|
|
575
|
+
};
|
|
576
|
+
}
|
|
646
577
|
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
647
578
|
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
648
579
|
if (await verifyKeyOwnership(key, {
|
|
@@ -672,6 +603,15 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
|
|
|
672
603
|
cached: false
|
|
673
604
|
};
|
|
674
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* Tells whether a key claims a portable actor as its owner. Such a key can
|
|
608
|
+
* only be a gateway key, which is never cached, so a cached one was cached
|
|
609
|
+
* by an older version that trusted web origins, and is fetched again.
|
|
610
|
+
*/
|
|
611
|
+
function claimsPortableOwner(key) {
|
|
612
|
+
const ownerId = key instanceof CryptographicKey ? key.ownerId : key?.controllerId;
|
|
613
|
+
return ownerId != null && isPortableId(ownerId);
|
|
614
|
+
}
|
|
675
615
|
async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
|
|
676
616
|
const start = performance.now();
|
|
677
617
|
let outcome = { result: "error" };
|
|
@@ -698,7 +638,7 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
698
638
|
outcome = { result: "hit" };
|
|
699
639
|
return cachedUnavailable;
|
|
700
640
|
}
|
|
701
|
-
if (cached != null) {
|
|
641
|
+
if (cached != null && !claimsPortableOwner(cached.key)) {
|
|
702
642
|
outcome = { result: "hit" };
|
|
703
643
|
return cached;
|
|
704
644
|
}
|
|
@@ -752,4 +692,4 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
|
752
692
|
});
|
|
753
693
|
}
|
|
754
694
|
//#endregion
|
|
755
|
-
export { generateCryptoKeyPair as a, verifyKeyOwnership as c,
|
|
695
|
+
export { generateCryptoKeyPair as a, verifyKeyOwnership as c, fetchKeyDetailed as i, fetchActorDocument as n, importJwk as o, fetchKey as r, validateCryptoKey as s, exportJwk as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
|
-
const require_http = require("./http-
|
|
2
|
+
const require_http = require("./http-DSaSAN7X.cjs");
|
|
3
3
|
let _logtape_logtape = require("@logtape/logtape");
|
|
4
4
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
5
5
|
let es_toolkit = require("es-toolkit");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { p as recordDocumentCache } from "./metrics-
|
|
4
|
+
import { p as recordDocumentCache } from "./metrics-B7YwKTFr.mjs";
|
|
5
5
|
import { getLogger } from "@logtape/logtape";
|
|
6
6
|
import { preloadedContexts } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/utils/kv-cache.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { B as validateCryptoKey, Q as recordDocumentCache, t as doubleKnock } from "./http-DyT7DP1Y.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
6
|
import { curry } from "es-toolkit";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import { r as fetchKey, s as validateCryptoKey } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-B7YwKTFr.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-Dwm4VswT.mjs";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
8
8
|
import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
|
|
9
9
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
4
|
+
import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
|
|
5
5
|
import { metrics } from "@opentelemetry/api";
|
|
6
6
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/federation/metrics.ts
|