@fedify/fedify 2.3.7 → 2.3.8
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/{assert_instance_of-DBC5X09g.mjs → assert-jSdZMYam.mjs} +19 -1
- package/dist/{builder-Bg7nkigw.mjs → builder-CDForRSL.mjs} +3 -3
- package/dist/{client-D1KuWxjH.mjs → client-DYDoDpMa.mjs} +78 -4
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +1 -1
- package/dist/compat/public-audience.test.mjs +1 -1
- package/dist/compat/transformers.test.mjs +3 -4
- package/dist/{context-DVoTs_wM.mjs → context-BepjCFHN.mjs} +1 -0
- package/dist/{context-BxVVO5Ro.d.ts → context-CAKnQy1x.d.ts} +4 -3
- package/dist/{context-BpmLyiPx.d.cts → context-CSIggG9_.d.cts} +4 -3
- package/dist/{deno-CTdCdRmf.mjs → deno--DCBC7WG.mjs} +1 -1
- package/dist/{docloader-BVBWf8Bg.mjs → docloader-BAdp-ZeK.mjs} +2 -2
- package/dist/federation/builder.test.mjs +2 -2
- package/dist/federation/circuit-breaker.test.mjs +3 -3
- package/dist/federation/collection.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +128 -11
- package/dist/federation/idempotency.test.mjs +4 -4
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/keycache.test.mjs +44 -10
- package/dist/federation/kv.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +109 -19
- 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/mq.test.mjs +1 -1
- package/dist/federation/negotiation.test.mjs +2 -2
- package/dist/federation/retry.test.mjs +1 -1
- package/dist/federation/router.test.mjs +1 -1
- package/dist/federation/send.test.mjs +141 -7
- package/dist/federation/temporal.test.mjs +2 -2
- package/dist/federation/webfinger.test.mjs +3 -3
- package/dist/{http-DRVY9K4R.js → http-BKJYj9oz.js} +155 -7
- package/dist/{http-BjV0BTWd.cjs → http-ChVf88XZ.cjs} +166 -6
- package/dist/{http-B3-zan5-.mjs → http-ClEDLGpr.mjs} +4 -4
- package/dist/{http-lf8Hsd91.d.ts → http-DQmNpfym.d.ts} +1 -1
- package/dist/{http-VyDTd4G3.d.cts → http-DiWQB8Mr.d.cts} +1 -1
- package/dist/{key-C_a6iE1-.mjs → key-C-7sqo8D.mjs} +155 -7
- package/dist/{keycache-BeU0LCII.mjs → keycache-B3qRQNLj.mjs} +12 -4
- package/dist/{kv-cache-BjjcmjtG.mjs → kv-cache-C5FK4PLG.mjs} +1 -1
- package/dist/{kv-cache-b1dJZHUx.js → kv-cache-C_w70bFh.js} +2 -2
- package/dist/{kv-cache-OzUoHKnZ.cjs → kv-cache-DFBOAyNj.cjs} +2 -2
- package/dist/{ld-COo05yJj.mjs → ld-CtFHB6rU.mjs} +3 -3
- package/dist/{metrics-V6vX0M13.mjs → metrics-FDcYXRP5.mjs} +1 -1
- package/dist/{middleware-EDVv6ewB.cjs → middleware-BdDtzhXb.cjs} +101 -25
- package/dist/{middleware-DRk6xiYo.mjs → middleware-COzI8bCL.mjs} +41 -30
- package/dist/{middleware-K13LhN5w.js → middleware-DunPXFJL.js} +101 -25
- package/dist/{middleware-DRGWEo0V.mjs → middleware-K7mJeIgT.mjs} +1 -1
- package/dist/{mod-yvIXFAEi.d.cts → mod-1TuoXkGs.d.cts} +1 -1
- package/dist/{mod-B0hW12_O.d.cts → mod-Bid79LFU.d.cts} +1 -1
- package/dist/{mod-Rpy4Uwkk.d.ts → mod-CFuyfw3c.d.ts} +1 -1
- package/dist/{mod-DFvNJcNb.d.ts → mod-DHgc4ygG.d.ts} +1 -1
- package/dist/{mod-CjERT09F.d.cts → mod-PVR75MeU.d.cts} +1 -1
- package/dist/{mod-COIAjwRS.d.ts → mod-TLEvgT1i.d.ts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +3 -3
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +1 -1
- package/dist/otel/exporter.test.mjs +1 -1
- package/dist/{outgoing-jsonld-OFoubetZ.mjs → outgoing-jsonld-HOAywiid.mjs} +1 -1
- package/dist/{owner-CnngXDNJ.d.ts → owner-B3hNQCG8.d.cts} +12 -1
- package/dist/owner-CGuS9lE7.mjs +175 -0
- package/dist/{owner-DEvZuyOE.d.cts → owner-CiBPcC6F.d.ts} +12 -1
- package/dist/{proof-kFq0IDK4.mjs → proof--56drUyt.mjs} +5 -5
- package/dist/{proof-Xk8lHNc0.cjs → proof-Dgozne0w.cjs} +112 -58
- package/dist/{proof-D6gZeewf.js → proof-tjbzynMa.js} +112 -58
- package/dist/{send-3lH63Nqf.mjs → send-CC1A7ATC.mjs} +68 -11
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/http.test.mjs +4 -4
- package/dist/sig/key.test.mjs +170 -3
- package/dist/sig/ld.test.mjs +56 -4
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +105 -6
- package/dist/sig/proof.test.mjs +73 -5
- package/dist/{temporal-Chwx9cEn.mjs → temporal-DvptXZ7V.mjs} +1 -1
- package/dist/testing/mod.mjs +1 -1
- package/dist/{types-m8u9L86X.cjs → types-DWs-qZaY.cjs} +94 -2
- package/dist/{types-DTONwNqL.js → types-sXITXcWa.js} +78 -4
- package/dist/utils/docloader.test.mjs +28 -4
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
- package/dist/assert-OguE97r2.mjs +0 -23
- package/dist/owner-B3720pz3.mjs +0 -120
- /package/dist/{accept-CPkZzmGN.mjs → accept-Dd__NiUL.mjs} +0 -0
- /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-ell7W1s9.mjs} +0 -0
- /package/dist/{circuit-breaker-D1fxthDI.mjs → circuit-breaker-CDABzqVf.mjs} +0 -0
- /package/dist/{circuit-breaker-test-utils-CAdwjgMi.mjs → circuit-breaker-test-utils-Dm5MM6l9.mjs} +0 -0
- /package/dist/{collection-Cc3DVAhE.mjs → collection-CA3V5zyK.mjs} +0 -0
- /package/dist/{keys-DGu1NFwu.mjs → keys-BAK-tUlf.mjs} +0 -0
- /package/dist/{kv-rV3vodCc.mjs → kv-CX8qfaad.mjs} +0 -0
- /package/dist/{negotiation-DDstyBvc.mjs → negotiation-CDW-_gUU.mjs} +0 -0
- /package/dist/{public-audience-DZJAUUi8.mjs → public-audience-jwoSIGUw.mjs} +0 -0
- /package/dist/{retry-CXg_MBI-.mjs → retry-_VvV0h9f.mjs} +0 -0
- /package/dist/{types-J53Kw7so.mjs → types-s4mCTGkT.mjs} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "@js-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--DCBC7WG.mjs";
|
|
5
|
+
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-FDcYXRP5.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -86,6 +86,118 @@ async function importJwk(jwk, type) {
|
|
|
86
86
|
validateCryptoKey(key, type);
|
|
87
87
|
return key;
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Fetches the document that the given actor URI dereferences to, and returns
|
|
91
|
+
* the actor only if the host that served the document is authoritative for
|
|
92
|
+
* the id the document claims.
|
|
93
|
+
*
|
|
94
|
+
* Only the origin that serves an actor id can speak for it. Without that
|
|
95
|
+
* rule any host could serve a document describing somebody else's actor—and
|
|
96
|
+
* listing its own keys as that actor's.
|
|
97
|
+
*
|
|
98
|
+
* @param actorId The URI of the actor to fetch.
|
|
99
|
+
* @param options Options for fetching the document.
|
|
100
|
+
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
101
|
+
* actor, or belongs to another origin.
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
async function fetchActorDocument(actorId, options = {}) {
|
|
105
|
+
const logger = getLogger([
|
|
106
|
+
"fedify",
|
|
107
|
+
"sig",
|
|
108
|
+
"key"
|
|
109
|
+
]);
|
|
110
|
+
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
111
|
+
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
112
|
+
const { tracerProvider } = options;
|
|
113
|
+
let document;
|
|
114
|
+
let documentUrl = actorId;
|
|
115
|
+
try {
|
|
116
|
+
const remoteDocument = await documentLoader(actorId.href);
|
|
117
|
+
document = remoteDocument.document;
|
|
118
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
logger.debug("Failed to fetch the actor {actorId}: {error}", {
|
|
121
|
+
actorId: actorId.href,
|
|
122
|
+
error
|
|
123
|
+
});
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
let object;
|
|
127
|
+
try {
|
|
128
|
+
object = await Object$1.fromJsonLd(document, {
|
|
129
|
+
documentLoader,
|
|
130
|
+
contextLoader,
|
|
131
|
+
tracerProvider,
|
|
132
|
+
baseUrl: documentUrl
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (!(error instanceof TypeError)) throw error;
|
|
136
|
+
logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
|
|
137
|
+
documentUrl: documentUrl.href,
|
|
138
|
+
error
|
|
139
|
+
});
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if (!isActor(object)) return null;
|
|
143
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
144
|
+
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.", {
|
|
145
|
+
documentUrl: documentUrl.href,
|
|
146
|
+
actorId: object.id?.href
|
|
147
|
+
});
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return object;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resolves the owner that the given key claims, and returns it only if the
|
|
154
|
+
* claim holds.
|
|
155
|
+
*
|
|
156
|
+
* The `owner` of a {@link CryptographicKey}, like the `controller` of
|
|
157
|
+
* a {@link Multikey}, proves nothing on its own: the key document and the
|
|
158
|
+
* claim inside it are served by the same host, so anyone able to serve a key
|
|
159
|
+
* document can name any actor in the world as its owner. The claim becomes
|
|
160
|
+
* meaningful only once the named actor's own document is fetched and turns
|
|
161
|
+
* out to link back to the key. This function performs that mutual-link
|
|
162
|
+
* check, which is the only sound way to learn a fetched key's owner.
|
|
163
|
+
*
|
|
164
|
+
* The claimed owner is always dereferenced, never read out of the key
|
|
165
|
+
* document: an owner embedded there is written by whoever wrote the key.
|
|
166
|
+
*
|
|
167
|
+
* @param key The key whose ownership claim is to be verified. It must carry
|
|
168
|
+
* an `id`, as that is what the owner has to link back to.
|
|
169
|
+
* @param options Options for fetching the claimed owner.
|
|
170
|
+
* @returns The verified owner, or `null` if the key claims no owner, the
|
|
171
|
+
* owner cannot be fetched, or the owner does not link back to
|
|
172
|
+
* the key.
|
|
173
|
+
* @internal
|
|
174
|
+
*/
|
|
175
|
+
async function verifyKeyOwnership(key, options = {}) {
|
|
176
|
+
const logger = getLogger([
|
|
177
|
+
"fedify",
|
|
178
|
+
"sig",
|
|
179
|
+
"key"
|
|
180
|
+
]);
|
|
181
|
+
const keyId = key.id;
|
|
182
|
+
if (keyId == null) return null;
|
|
183
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
184
|
+
if (claimedOwnerId == null) return null;
|
|
185
|
+
const owner = await fetchActorDocument(claimedOwnerId, options);
|
|
186
|
+
if (owner == null) {
|
|
187
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
188
|
+
keyId: keyId.href,
|
|
189
|
+
claimedOwnerId: claimedOwnerId.href
|
|
190
|
+
});
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const linkedKeyIds = key instanceof CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
|
|
194
|
+
for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
|
|
195
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
|
|
196
|
+
keyId: keyId.href,
|
|
197
|
+
claimedOwnerId: claimedOwnerId.href
|
|
198
|
+
});
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
89
201
|
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
90
202
|
tracerProvider ??= trace.getTracerProvider();
|
|
91
203
|
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
@@ -213,7 +325,7 @@ async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
|
|
|
213
325
|
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
214
326
|
await keyCache?.setFetchError?.(keyId, null);
|
|
215
327
|
}
|
|
216
|
-
async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
328
|
+
async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
217
329
|
let object;
|
|
218
330
|
try {
|
|
219
331
|
object = await Object$1.fromJsonLd(document, {
|
|
@@ -243,8 +355,22 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
243
355
|
}
|
|
244
356
|
}
|
|
245
357
|
let key = null;
|
|
246
|
-
|
|
358
|
+
let ownerDocument = null;
|
|
359
|
+
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
247
360
|
else if (isActor(object)) {
|
|
361
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
362
|
+
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
363
|
+
keyId,
|
|
364
|
+
documentUrl: documentUrl.href,
|
|
365
|
+
actorId: object.id?.href
|
|
366
|
+
});
|
|
367
|
+
await keyCache?.set(cacheKey, null);
|
|
368
|
+
return {
|
|
369
|
+
key: null,
|
|
370
|
+
cached: false
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
ownerDocument = object;
|
|
248
374
|
const keys = cls === CryptographicKey ? object.getPublicKeys({
|
|
249
375
|
documentLoader,
|
|
250
376
|
contextLoader,
|
|
@@ -306,6 +432,25 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
306
432
|
cached: false
|
|
307
433
|
};
|
|
308
434
|
}
|
|
435
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
436
|
+
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
437
|
+
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
438
|
+
if (await verifyKeyOwnership(key, {
|
|
439
|
+
documentLoader,
|
|
440
|
+
contextLoader,
|
|
441
|
+
tracerProvider
|
|
442
|
+
}) == null) {
|
|
443
|
+
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
444
|
+
keyId,
|
|
445
|
+
claimedOwnerId: claimedOwnerId.href
|
|
446
|
+
});
|
|
447
|
+
await keyCache?.set(cacheKey, null);
|
|
448
|
+
return {
|
|
449
|
+
key: null,
|
|
450
|
+
cached: false
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
309
454
|
if (keyCache != null) {
|
|
310
455
|
await keyCache.set(cacheKey, key);
|
|
311
456
|
logger.debug("Key {keyId} cached.", { keyId });
|
|
@@ -339,15 +484,18 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
339
484
|
}
|
|
340
485
|
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
341
486
|
let document;
|
|
487
|
+
let documentUrl = cacheKey;
|
|
342
488
|
try {
|
|
343
|
-
|
|
489
|
+
const remoteDocument = await (options.documentLoader ?? getDocumentLoader())(keyId);
|
|
490
|
+
document = remoteDocument.document;
|
|
491
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
344
492
|
} catch (error) {
|
|
345
493
|
const classified = classifyFetchError(error);
|
|
346
494
|
const errored = await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
347
495
|
outcome = classified;
|
|
348
496
|
return errored;
|
|
349
497
|
}
|
|
350
|
-
const resolved = await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
498
|
+
const resolved = await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
351
499
|
outcome = { result: resolved.key != null ? "fetched" : "invalid" };
|
|
352
500
|
return resolved;
|
|
353
501
|
} finally {
|
|
@@ -384,4 +532,4 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
|
384
532
|
});
|
|
385
533
|
}
|
|
386
534
|
//#endregion
|
|
387
|
-
export {
|
|
535
|
+
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 };
|
|
@@ -3,6 +3,7 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
5
5
|
//#region src/federation/keycache.ts
|
|
6
|
+
const KEY_CACHE_GENERATION = "2";
|
|
6
7
|
var KvKeyCache = class {
|
|
7
8
|
kv;
|
|
8
9
|
prefix;
|
|
@@ -23,13 +24,20 @@ var KvKeyCache = class {
|
|
|
23
24
|
keyId.href
|
|
24
25
|
];
|
|
25
26
|
}
|
|
27
|
+
#entryKey(keyId) {
|
|
28
|
+
return [
|
|
29
|
+
...this.prefix,
|
|
30
|
+
KEY_CACHE_GENERATION,
|
|
31
|
+
keyId.href
|
|
32
|
+
];
|
|
33
|
+
}
|
|
26
34
|
async get(keyId) {
|
|
27
35
|
const negativeExpiration = this.nullKeys.get(keyId.href);
|
|
28
36
|
if (negativeExpiration != null) {
|
|
29
37
|
if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
|
|
30
38
|
this.nullKeys.delete(keyId.href);
|
|
31
39
|
}
|
|
32
|
-
const serialized = await this.kv.get(
|
|
40
|
+
const serialized = await this.kv.get(this.#entryKey(keyId));
|
|
33
41
|
if (serialized === void 0) return void 0;
|
|
34
42
|
if (serialized === null) {
|
|
35
43
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
@@ -41,7 +49,7 @@ var KvKeyCache = class {
|
|
|
41
49
|
try {
|
|
42
50
|
return await Multikey.fromJsonLd(serialized, this.options);
|
|
43
51
|
} catch {
|
|
44
|
-
await this.kv.delete(
|
|
52
|
+
await this.kv.delete(this.#entryKey(keyId));
|
|
45
53
|
return;
|
|
46
54
|
}
|
|
47
55
|
}
|
|
@@ -49,12 +57,12 @@ var KvKeyCache = class {
|
|
|
49
57
|
async set(keyId, key) {
|
|
50
58
|
if (key == null) {
|
|
51
59
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
52
|
-
await this.kv.set(
|
|
60
|
+
await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
|
|
53
61
|
return;
|
|
54
62
|
}
|
|
55
63
|
this.nullKeys.delete(keyId.href);
|
|
56
64
|
const serialized = await key.toJsonLd(this.options);
|
|
57
|
-
await this.kv.set(
|
|
65
|
+
await this.kv.set(this.#entryKey(keyId), serialized);
|
|
58
66
|
}
|
|
59
67
|
async getFetchError(keyId) {
|
|
60
68
|
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "@js-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-FDcYXRP5.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,9 +1,9 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { T as recordDocumentCache, f as validateCryptoKey, t as doubleKnock } from "./http-BKJYj9oz.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
|
-
import { curry } from "es-toolkit";
|
|
6
5
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
|
+
import { curry } from "es-toolkit";
|
|
7
7
|
//#region src/utils/docloader.ts
|
|
8
8
|
const logger$1 = getLogger([
|
|
9
9
|
"fedify",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
require("./chunk-DDcVe30Y.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-ChVf88XZ.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
|
-
let es_toolkit = require("es-toolkit");
|
|
7
6
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
7
|
+
let es_toolkit = require("es-toolkit");
|
|
8
8
|
//#region src/utils/docloader.ts
|
|
9
9
|
const logger$1 = (0, _logtape_logtape.getLogger)([
|
|
10
10
|
"fedify",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "@js-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 {
|
|
4
|
+
import { n as version, t as name } from "./deno--DCBC7WG.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-FDcYXRP5.mjs";
|
|
6
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-C-7sqo8D.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 "@js-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--DCBC7WG.mjs";
|
|
5
5
|
import { metrics } from "@opentelemetry/api";
|
|
6
6
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/federation/metrics.ts
|
|
@@ -2,17 +2,17 @@ const { Temporal } = require("@js-temporal/polyfill");
|
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
const require_chunk = require("./chunk-DDcVe30Y.cjs");
|
|
4
4
|
const require_transformers = require("./transformers-NeAONrAq.cjs");
|
|
5
|
-
const require_http = require("./http-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const require_kv_cache = require("./kv-cache-
|
|
5
|
+
const require_http = require("./http-ChVf88XZ.cjs");
|
|
6
|
+
const require_types = require("./types-DWs-qZaY.cjs");
|
|
7
|
+
const require_proof = require("./proof-Dgozne0w.cjs");
|
|
8
|
+
const require_kv_cache = require("./kv-cache-DFBOAyNj.cjs");
|
|
9
9
|
let _logtape_logtape = require("@logtape/logtape");
|
|
10
10
|
let _fedify_uri_template = require("@fedify/uri-template");
|
|
11
11
|
let _fedify_vocab = require("@fedify/vocab");
|
|
12
12
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
13
13
|
let byte_encodings_hex = require("byte-encodings/hex");
|
|
14
|
-
let es_toolkit = require("es-toolkit");
|
|
15
14
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
15
|
+
let es_toolkit = require("es-toolkit");
|
|
16
16
|
let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
|
|
17
17
|
let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
|
|
18
18
|
_fedify_vocab_runtime_jsonld = require_chunk.__toESM(_fedify_vocab_runtime_jsonld, 1);
|
|
@@ -1414,6 +1414,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
1414
1414
|
}
|
|
1415
1415
|
//#endregion
|
|
1416
1416
|
//#region src/federation/keycache.ts
|
|
1417
|
+
const KEY_CACHE_GENERATION = "2";
|
|
1417
1418
|
var KvKeyCache = class {
|
|
1418
1419
|
kv;
|
|
1419
1420
|
prefix;
|
|
@@ -1434,13 +1435,20 @@ var KvKeyCache = class {
|
|
|
1434
1435
|
keyId.href
|
|
1435
1436
|
];
|
|
1436
1437
|
}
|
|
1438
|
+
#entryKey(keyId) {
|
|
1439
|
+
return [
|
|
1440
|
+
...this.prefix,
|
|
1441
|
+
KEY_CACHE_GENERATION,
|
|
1442
|
+
keyId.href
|
|
1443
|
+
];
|
|
1444
|
+
}
|
|
1437
1445
|
async get(keyId) {
|
|
1438
1446
|
const negativeExpiration = this.nullKeys.get(keyId.href);
|
|
1439
1447
|
if (negativeExpiration != null) {
|
|
1440
1448
|
if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
|
|
1441
1449
|
this.nullKeys.delete(keyId.href);
|
|
1442
1450
|
}
|
|
1443
|
-
const serialized = await this.kv.get(
|
|
1451
|
+
const serialized = await this.kv.get(this.#entryKey(keyId));
|
|
1444
1452
|
if (serialized === void 0) return void 0;
|
|
1445
1453
|
if (serialized === null) {
|
|
1446
1454
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
@@ -1452,7 +1460,7 @@ var KvKeyCache = class {
|
|
|
1452
1460
|
try {
|
|
1453
1461
|
return await _fedify_vocab.Multikey.fromJsonLd(serialized, this.options);
|
|
1454
1462
|
} catch {
|
|
1455
|
-
await this.kv.delete(
|
|
1463
|
+
await this.kv.delete(this.#entryKey(keyId));
|
|
1456
1464
|
return;
|
|
1457
1465
|
}
|
|
1458
1466
|
}
|
|
@@ -1460,12 +1468,12 @@ var KvKeyCache = class {
|
|
|
1460
1468
|
async set(keyId, key) {
|
|
1461
1469
|
if (key == null) {
|
|
1462
1470
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
1463
|
-
await this.kv.set(
|
|
1471
|
+
await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
|
|
1464
1472
|
return;
|
|
1465
1473
|
}
|
|
1466
1474
|
this.nullKeys.delete(keyId.href);
|
|
1467
1475
|
const serialized = await key.toJsonLd(this.options);
|
|
1468
|
-
await this.kv.set(
|
|
1476
|
+
await this.kv.set(this.#entryKey(keyId), serialized);
|
|
1469
1477
|
}
|
|
1470
1478
|
async getFetchError(keyId) {
|
|
1471
1479
|
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
|
|
@@ -1812,13 +1820,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
|
|
|
1812
1820
|
* @param parameters The parameters for handling the collection.
|
|
1813
1821
|
* @returns A promise that resolves to an HTTP response.
|
|
1814
1822
|
*/
|
|
1815
|
-
async function handleCollection(request, { name: name$
|
|
1816
|
-
const spanName = name$
|
|
1823
|
+
async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
|
|
1824
|
+
const spanName = name$2.trim().replace(/\s+/g, "_");
|
|
1817
1825
|
tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
|
|
1818
1826
|
const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
|
|
1819
1827
|
const cursor = new URL(request.url).searchParams.get("cursor");
|
|
1820
1828
|
const metricBase = {
|
|
1821
|
-
kind: getCollectionMetricKind(name$
|
|
1829
|
+
kind: getCollectionMetricKind(name$2),
|
|
1822
1830
|
page: cursor != null,
|
|
1823
1831
|
dispatcher: "built_in"
|
|
1824
1832
|
};
|
|
@@ -1859,7 +1867,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
1859
1867
|
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
1860
1868
|
return await onNotFound(request);
|
|
1861
1869
|
}
|
|
1862
|
-
const items = filterCollectionItems(page.items, name$
|
|
1870
|
+
const items = filterCollectionItems(page.items, name$2, filterPredicate);
|
|
1863
1871
|
itemCount = items.length;
|
|
1864
1872
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
1865
1873
|
return items;
|
|
@@ -1899,7 +1907,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
1899
1907
|
} else {
|
|
1900
1908
|
const uri = new URL(baseUri);
|
|
1901
1909
|
uri.searchParams.set("cursor", cursor);
|
|
1902
|
-
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$
|
|
1910
|
+
const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
|
|
1903
1911
|
kind: _opentelemetry_api.SpanKind.SERVER,
|
|
1904
1912
|
attributes: {
|
|
1905
1913
|
"activitypub.collection.id": uri.href,
|
|
@@ -1915,7 +1923,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
|
|
|
1915
1923
|
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
|
|
1916
1924
|
return await onNotFound(request);
|
|
1917
1925
|
}
|
|
1918
|
-
const items = filterCollectionItems(page.items, name$
|
|
1926
|
+
const items = filterCollectionItems(page.items, name$2, filterPredicate);
|
|
1919
1927
|
itemCount = items.length;
|
|
1920
1928
|
span.setAttribute("fedify.collection.items", itemCount);
|
|
1921
1929
|
return {
|
|
@@ -2281,8 +2289,16 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
2281
2289
|
}
|
|
2282
2290
|
let json;
|
|
2283
2291
|
try {
|
|
2284
|
-
json = await request.clone().
|
|
2292
|
+
json = JSON.parse(await require_types.readBoundedText(request.clone(), require_types.MAX_BODY_SIZE, request.url));
|
|
2285
2293
|
} catch (error) {
|
|
2294
|
+
if (error instanceof require_types.BodyTooLargeError) {
|
|
2295
|
+
request.body?.cancel(error).catch(() => {});
|
|
2296
|
+
span.setStatus({
|
|
2297
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
2298
|
+
message: String(error)
|
|
2299
|
+
});
|
|
2300
|
+
return new Response("Inbox body too large.", { status: 413 });
|
|
2301
|
+
}
|
|
2286
2302
|
logger.error("Failed to parse JSON:\n{error}", {
|
|
2287
2303
|
recipient,
|
|
2288
2304
|
error
|
|
@@ -2783,8 +2799,8 @@ var CustomCollectionHandler = class {
|
|
|
2783
2799
|
* @param CollectionPage The CollectionPage constructor.
|
|
2784
2800
|
* @param filterPredicate Optional filter predicate for items.
|
|
2785
2801
|
*/
|
|
2786
|
-
constructor(name$
|
|
2787
|
-
this.name = name$
|
|
2802
|
+
constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
|
|
2803
|
+
this.name = name$1;
|
|
2788
2804
|
this.values = values;
|
|
2789
2805
|
this.context = context;
|
|
2790
2806
|
this.callbacks = callbacks;
|
|
@@ -3409,7 +3425,7 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
3409
3425
|
if (truncated) result += "… (truncated)";
|
|
3410
3426
|
return result;
|
|
3411
3427
|
}
|
|
3412
|
-
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
3428
|
+
async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
|
|
3413
3429
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
3414
3430
|
"fedify",
|
|
3415
3431
|
"federation",
|
|
@@ -3418,12 +3434,25 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
3418
3434
|
const federationMetrics = require_http.getFederationMetrics(meterProvider);
|
|
3419
3435
|
const started = performance.now();
|
|
3420
3436
|
let deliverySuccess = false;
|
|
3437
|
+
async function validateUrl(url) {
|
|
3438
|
+
if (!allowPrivateAddress) try {
|
|
3439
|
+
await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
|
|
3440
|
+
} catch (error) {
|
|
3441
|
+
if (error instanceof _fedify_vocab_runtime.UrlError) logger.error("Disallowed private URL: {url}", {
|
|
3442
|
+
url,
|
|
3443
|
+
error
|
|
3444
|
+
});
|
|
3445
|
+
throw error;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
await validateUrl(inbox.href);
|
|
3421
3449
|
headers = new Headers(headers);
|
|
3422
3450
|
headers.set("Content-Type", "application/activity+json");
|
|
3423
3451
|
const request = new Request(inbox, {
|
|
3424
3452
|
method: "POST",
|
|
3425
3453
|
headers,
|
|
3426
|
-
body: JSON.stringify(activity)
|
|
3454
|
+
body: JSON.stringify(activity),
|
|
3455
|
+
redirect: "manual"
|
|
3427
3456
|
});
|
|
3428
3457
|
let rsaKey = null;
|
|
3429
3458
|
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
@@ -3439,19 +3468,20 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
|
|
|
3439
3468
|
});
|
|
3440
3469
|
let response;
|
|
3441
3470
|
try {
|
|
3442
|
-
response = rsaKey == null ? await
|
|
3471
|
+
response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await require_http.doubleKnock(request, rsaKey, {
|
|
3443
3472
|
tracerProvider,
|
|
3444
|
-
specDeterminer
|
|
3473
|
+
specDeterminer,
|
|
3474
|
+
validateRedirect: validateUrl
|
|
3445
3475
|
});
|
|
3446
3476
|
} catch (error) {
|
|
3447
|
-
const
|
|
3477
|
+
const failure = error instanceof _fedify_vocab_runtime.FetchError || error instanceof _fedify_vocab_runtime.UrlError ? error : createFetchError(inbox.href, error);
|
|
3448
3478
|
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
3449
3479
|
activityId,
|
|
3450
3480
|
inbox: inbox.href,
|
|
3451
|
-
error:
|
|
3481
|
+
error: failure
|
|
3452
3482
|
});
|
|
3453
3483
|
federationMetrics.recordDelivery(inbox, require_http.getDurationMs(started), false, activityType);
|
|
3454
|
-
throw
|
|
3484
|
+
throw failure;
|
|
3455
3485
|
}
|
|
3456
3486
|
try {
|
|
3457
3487
|
if (!response.ok) {
|
|
@@ -3488,6 +3518,49 @@ function createFetchError(url, cause) {
|
|
|
3488
3518
|
error.cause = cause;
|
|
3489
3519
|
return error;
|
|
3490
3520
|
}
|
|
3521
|
+
async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
3522
|
+
let body = await request.clone().text();
|
|
3523
|
+
for (let redirects = 0;; redirects++) {
|
|
3524
|
+
const response = await fetch(request, { redirect: "manual" });
|
|
3525
|
+
const location = response.headers.get("Location");
|
|
3526
|
+
if (![
|
|
3527
|
+
301,
|
|
3528
|
+
302,
|
|
3529
|
+
303,
|
|
3530
|
+
307,
|
|
3531
|
+
308
|
|
3532
|
+
].includes(response.status) || location == null) return response;
|
|
3533
|
+
await response.body?.cancel();
|
|
3534
|
+
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
3535
|
+
const url = new URL(location, request.url);
|
|
3536
|
+
await validateUrl(url.href);
|
|
3537
|
+
const headers = new Headers(request.headers);
|
|
3538
|
+
if (url.origin !== new URL(request.url).origin) {
|
|
3539
|
+
headers.delete("Authorization");
|
|
3540
|
+
headers.delete("Proxy-Authorization");
|
|
3541
|
+
headers.delete("Cookie");
|
|
3542
|
+
headers.delete("Host");
|
|
3543
|
+
}
|
|
3544
|
+
let method = request.method;
|
|
3545
|
+
if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
|
|
3546
|
+
method = "GET";
|
|
3547
|
+
body = void 0;
|
|
3548
|
+
for (const name of [
|
|
3549
|
+
"Content-Encoding",
|
|
3550
|
+
"Content-Language",
|
|
3551
|
+
"Content-Length",
|
|
3552
|
+
"Content-Location",
|
|
3553
|
+
"Content-Type"
|
|
3554
|
+
]) headers.delete(name);
|
|
3555
|
+
}
|
|
3556
|
+
request = new Request(url, {
|
|
3557
|
+
method,
|
|
3558
|
+
headers,
|
|
3559
|
+
body,
|
|
3560
|
+
redirect: "manual"
|
|
3561
|
+
});
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3491
3564
|
/**
|
|
3492
3565
|
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
3493
3566
|
* It contains structured information about the failure, including the HTTP
|
|
@@ -4633,6 +4706,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4633
4706
|
if (decision.stateChange != null) recordCircuitBreakerSpanEvent(span, remoteHost, decision.stateChange);
|
|
4634
4707
|
}
|
|
4635
4708
|
await sendActivity({
|
|
4709
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
4636
4710
|
keys,
|
|
4637
4711
|
activity: message.activity,
|
|
4638
4712
|
activityId: message.activityId,
|
|
@@ -5155,6 +5229,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5155
5229
|
});
|
|
5156
5230
|
const promises = [];
|
|
5157
5231
|
for (const inbox in inboxes) promises.push(sendActivity({
|
|
5232
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
5158
5233
|
keys,
|
|
5159
5234
|
activity: jsonLd,
|
|
5160
5235
|
activityId: activity.id?.href,
|
|
@@ -6432,6 +6507,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
6432
6507
|
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
6433
6508
|
const promises = [];
|
|
6434
6509
|
for (const inbox in inboxes) promises.push(sendActivity({
|
|
6510
|
+
allowPrivateAddress: ctx.federation.allowPrivateAddress,
|
|
6435
6511
|
keys,
|
|
6436
6512
|
activity: ctx.activity,
|
|
6437
6513
|
activityId: ctx.activityId,
|