@fedify/fedify 2.2.12 → 2.2.14-dev.2082
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-C4Ri6VuN.mjs → assert-BhQd8QHP.mjs} +19 -1
- package/dist/{builder-TJzcpmDm.mjs → builder-Cwgo1nK5.mjs} +3 -3
- package/dist/{client-dx126nMS.mjs → client-CQ7zd8tW.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-BU-1O90h.d.ts → context-Bt2jnbC7.d.ts} +4 -3
- package/dist/{context-DVA8wHZ0.d.cts → context-Cls_M1HU.d.cts} +4 -3
- package/dist/{deno-DeYUdFal.mjs → deno-CV4wsQzy.mjs} +1 -1
- package/dist/{docloader-CXH2XqYP.mjs → docloader-DV9F08Dn.mjs} +2 -2
- package/dist/federation/builder.test.mjs +2 -2
- package/dist/federation/collection.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +127 -10
- 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/middleware.test.mjs +103 -15
- 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 +138 -6
- package/dist/federation/temporal.test.mjs +2 -2
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{http-D6cIKrI1.cjs → http-BWNuy1sb.cjs} +166 -6
- package/dist/{http-CNsxTbVr.js → http-C5T-lWqB.js} +155 -7
- package/dist/{http-79lZeYEo.mjs → http-C9MzFrOj.mjs} +3 -3
- package/dist/{http-BDZeS5om.d.ts → http-Dl00HXr4.d.ts} +1 -1
- package/dist/{http-C87EWkO0.d.cts → http-DqZhyEso.d.cts} +1 -1
- package/dist/{key-B4kUuy3o.mjs → key-DiziIx9e.mjs} +154 -6
- package/dist/{keycache-EGATflN-.mjs → keycache-C1aVprwy.mjs} +12 -4
- package/dist/{kv-cache-CIZ1m-94.js → kv-cache-CM-zJoep.js} +1 -1
- package/dist/{kv-cache-BtXEUvHT.cjs → kv-cache-DGPEHc1T.cjs} +1 -1
- package/dist/{ld-2R9vZ_ym.mjs → ld-BAeuDKjx.mjs} +2 -2
- package/dist/{middleware-CjrIRAsQ.cjs → middleware-BbbKc-Tr.cjs} +90 -14
- package/dist/{middleware-D5NE6PkU.mjs → middleware-CkJOjZjS.mjs} +31 -20
- package/dist/{middleware-_qt3kHUU.mjs → middleware-DA2CvTQL.mjs} +1 -1
- package/dist/{middleware-DsEAPFhi.js → middleware-J32whqNP.js} +90 -14
- package/dist/{middleware-2bSnAJOQ.cjs → middleware-XNvq3NUZ.cjs} +1 -1
- package/dist/{mod-BhU_H1I_.d.ts → mod-0MWo2Nu5.d.ts} +1 -1
- package/dist/{mod-DXY9JF28.d.cts → mod-D2JyWtW3.d.cts} +1 -1
- package/dist/{mod-q-NFLW6B.d.cts → mod-DNvnDTS7.d.cts} +1 -1
- package/dist/{mod-BVt6iTmH.d.ts → mod-DaiJDJdQ.d.ts} +1 -1
- package/dist/{mod-B0rWmfW5.d.cts → mod-DzPMtIV3.d.cts} +1 -1
- package/dist/{mod-DHO9lk3D.d.ts → mod-ej_rLx8h.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 +2 -2
- 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-CfUItkGW.mjs → outgoing-jsonld-Dc2OXyB-.mjs} +1 -1
- package/dist/{owner-CnngXDNJ.d.ts → owner-B3hNQCG8.d.cts} +12 -1
- package/dist/{owner-DEvZuyOE.d.cts → owner-CiBPcC6F.d.ts} +12 -1
- package/dist/owner-D08_JUSb.mjs +175 -0
- package/dist/{proof-CmtXtaul.cjs → proof-7T47tIu5.cjs} +112 -58
- package/dist/{proof-BhW_iARR.js → proof-BdRnLnh7.js} +112 -58
- package/dist/{proof-BzTt6nlk.mjs → proof-_S-wFUFM.mjs} +4 -4
- package/dist/{send-CZwOs7XD.mjs → send-DmcKKipr.mjs} +64 -6
- 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-BYCnXIGk.mjs → temporal-CX_L28b-.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 +5 -5
- package/dist/assert-DikXweDx.mjs +0 -23
- package/dist/owner-CqYsVXcd.mjs +0 -120
- /package/dist/{accept-CPkZzmGN.mjs → accept-Dd__NiUL.mjs} +0 -0
- /package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-IojJ9SVl.mjs} +0 -0
- /package/dist/{collection-D-HqUuA2.mjs → collection-DnoYNwBC.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/{kv-cache-DYsF2MhP.mjs → kv-cache-FsT1ljhs.mjs} +0 -0
- /package/dist/{negotiation-SQvQgUqe.mjs → negotiation-BHtZN6Y1.mjs} +0 -0
- /package/dist/{public-audience-Ci-r-4Q-.mjs → public-audience-CS1udGkN.mjs} +0 -0
- /package/dist/{retry-v_sGLH1d.mjs → retry-bMXBL97A.mjs} +0 -0
- /package/dist/{types-J53Kw7so.mjs → types-s4mCTGkT.mjs} +0 -0
|
@@ -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-CV4wsQzy.mjs";
|
|
5
5
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
6
6
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
7
7
|
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
@@ -85,6 +85,118 @@ async function importJwk(jwk, type) {
|
|
|
85
85
|
validateCryptoKey(key, type);
|
|
86
86
|
return key;
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Fetches the document that the given actor URI dereferences to, and returns
|
|
90
|
+
* the actor only if the host that served the document is authoritative for
|
|
91
|
+
* the id the document claims.
|
|
92
|
+
*
|
|
93
|
+
* Only the origin that serves an actor id can speak for it. Without that
|
|
94
|
+
* rule any host could serve a document describing somebody else's actor—and
|
|
95
|
+
* listing its own keys as that actor's.
|
|
96
|
+
*
|
|
97
|
+
* @param actorId The URI of the actor to fetch.
|
|
98
|
+
* @param options Options for fetching the document.
|
|
99
|
+
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
100
|
+
* actor, or belongs to another origin.
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
async function fetchActorDocument(actorId, options = {}) {
|
|
104
|
+
const logger = getLogger([
|
|
105
|
+
"fedify",
|
|
106
|
+
"sig",
|
|
107
|
+
"key"
|
|
108
|
+
]);
|
|
109
|
+
const documentLoader = options.documentLoader ?? getDocumentLoader();
|
|
110
|
+
const contextLoader = options.contextLoader ?? getDocumentLoader();
|
|
111
|
+
const { tracerProvider } = options;
|
|
112
|
+
let document;
|
|
113
|
+
let documentUrl = actorId;
|
|
114
|
+
try {
|
|
115
|
+
const remoteDocument = await documentLoader(actorId.href);
|
|
116
|
+
document = remoteDocument.document;
|
|
117
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", actorId);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
logger.debug("Failed to fetch the actor {actorId}: {error}", {
|
|
120
|
+
actorId: actorId.href,
|
|
121
|
+
error
|
|
122
|
+
});
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
let object;
|
|
126
|
+
try {
|
|
127
|
+
object = await Object$1.fromJsonLd(document, {
|
|
128
|
+
documentLoader,
|
|
129
|
+
contextLoader,
|
|
130
|
+
tracerProvider,
|
|
131
|
+
baseUrl: documentUrl
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (!(error instanceof TypeError)) throw error;
|
|
135
|
+
logger.debug("The document served at {documentUrl} is not a valid object: {error}", {
|
|
136
|
+
documentUrl: documentUrl.href,
|
|
137
|
+
error
|
|
138
|
+
});
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
if (!isActor(object)) return null;
|
|
142
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
143
|
+
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.", {
|
|
144
|
+
documentUrl: documentUrl.href,
|
|
145
|
+
actorId: object.id?.href
|
|
146
|
+
});
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return object;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Resolves the owner that the given key claims, and returns it only if the
|
|
153
|
+
* claim holds.
|
|
154
|
+
*
|
|
155
|
+
* The `owner` of a {@link CryptographicKey}, like the `controller` of
|
|
156
|
+
* a {@link Multikey}, proves nothing on its own: the key document and the
|
|
157
|
+
* claim inside it are served by the same host, so anyone able to serve a key
|
|
158
|
+
* document can name any actor in the world as its owner. The claim becomes
|
|
159
|
+
* meaningful only once the named actor's own document is fetched and turns
|
|
160
|
+
* out to link back to the key. This function performs that mutual-link
|
|
161
|
+
* check, which is the only sound way to learn a fetched key's owner.
|
|
162
|
+
*
|
|
163
|
+
* The claimed owner is always dereferenced, never read out of the key
|
|
164
|
+
* document: an owner embedded there is written by whoever wrote the key.
|
|
165
|
+
*
|
|
166
|
+
* @param key The key whose ownership claim is to be verified. It must carry
|
|
167
|
+
* an `id`, as that is what the owner has to link back to.
|
|
168
|
+
* @param options Options for fetching the claimed owner.
|
|
169
|
+
* @returns The verified owner, or `null` if the key claims no owner, the
|
|
170
|
+
* owner cannot be fetched, or the owner does not link back to
|
|
171
|
+
* the key.
|
|
172
|
+
* @internal
|
|
173
|
+
*/
|
|
174
|
+
async function verifyKeyOwnership(key, options = {}) {
|
|
175
|
+
const logger = getLogger([
|
|
176
|
+
"fedify",
|
|
177
|
+
"sig",
|
|
178
|
+
"key"
|
|
179
|
+
]);
|
|
180
|
+
const keyId = key.id;
|
|
181
|
+
if (keyId == null) return null;
|
|
182
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
183
|
+
if (claimedOwnerId == null) return null;
|
|
184
|
+
const owner = await fetchActorDocument(claimedOwnerId, options);
|
|
185
|
+
if (owner == null) {
|
|
186
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims could not be resolved.", {
|
|
187
|
+
keyId: keyId.href,
|
|
188
|
+
claimedOwnerId: claimedOwnerId.href
|
|
189
|
+
});
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const linkedKeyIds = key instanceof CryptographicKey ? owner.publicKeyIds : owner.assertionMethodIds;
|
|
193
|
+
for (const linkedKeyId of linkedKeyIds) if (linkedKeyId.href === keyId.href) return owner;
|
|
194
|
+
logger.debug("The owner ({claimedOwnerId}) that key {keyId} claims does not list the key as its own.", {
|
|
195
|
+
keyId: keyId.href,
|
|
196
|
+
claimedOwnerId: claimedOwnerId.href
|
|
197
|
+
});
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
88
200
|
async function withFetchKeySpan(keyId, tracerProvider, fetcher) {
|
|
89
201
|
tracerProvider ??= trace.getTracerProvider();
|
|
90
202
|
return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.fetch_key", {
|
|
@@ -212,7 +324,7 @@ async function getCachedFetchKey(cacheKey, keyId, cls, keyCache, logger) {
|
|
|
212
324
|
async function clearFetchErrorMetadata(keyId, keyCache) {
|
|
213
325
|
await keyCache?.setFetchError?.(keyId, null);
|
|
214
326
|
}
|
|
215
|
-
async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
327
|
+
async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, { documentLoader, contextLoader, keyCache, tracerProvider }, logger) {
|
|
216
328
|
let object;
|
|
217
329
|
try {
|
|
218
330
|
object = await Object$1.fromJsonLd(document, {
|
|
@@ -242,8 +354,22 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
242
354
|
}
|
|
243
355
|
}
|
|
244
356
|
let key = null;
|
|
245
|
-
|
|
357
|
+
let ownerDocument = null;
|
|
358
|
+
if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
|
|
246
359
|
else if (isActor(object)) {
|
|
360
|
+
if (object.id == null || object.id.origin !== documentUrl.origin) {
|
|
361
|
+
logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
|
|
362
|
+
keyId,
|
|
363
|
+
documentUrl: documentUrl.href,
|
|
364
|
+
actorId: object.id?.href
|
|
365
|
+
});
|
|
366
|
+
await keyCache?.set(cacheKey, null);
|
|
367
|
+
return {
|
|
368
|
+
key: null,
|
|
369
|
+
cached: false
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
ownerDocument = object;
|
|
247
373
|
const keys = cls === CryptographicKey ? object.getPublicKeys({
|
|
248
374
|
documentLoader,
|
|
249
375
|
contextLoader,
|
|
@@ -305,6 +431,25 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
305
431
|
cached: false
|
|
306
432
|
};
|
|
307
433
|
}
|
|
434
|
+
const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
|
|
435
|
+
if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
|
|
436
|
+
else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
|
|
437
|
+
if (await verifyKeyOwnership(key, {
|
|
438
|
+
documentLoader,
|
|
439
|
+
contextLoader,
|
|
440
|
+
tracerProvider
|
|
441
|
+
}) == null) {
|
|
442
|
+
logger.debug("Failed to verify; the owner {claimedOwnerId} that key {keyId} claims does not list the key as its own.", {
|
|
443
|
+
keyId,
|
|
444
|
+
claimedOwnerId: claimedOwnerId.href
|
|
445
|
+
});
|
|
446
|
+
await keyCache?.set(cacheKey, null);
|
|
447
|
+
return {
|
|
448
|
+
key: null,
|
|
449
|
+
cached: false
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
308
453
|
if (keyCache != null) {
|
|
309
454
|
await keyCache.set(cacheKey, key);
|
|
310
455
|
logger.debug("Key {keyId} cached.", { keyId });
|
|
@@ -328,12 +473,15 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
|
|
|
328
473
|
if (cached != null) return cached;
|
|
329
474
|
logger.debug("Fetching key {keyId} to verify signature...", { keyId });
|
|
330
475
|
let document;
|
|
476
|
+
let documentUrl = cacheKey;
|
|
331
477
|
try {
|
|
332
|
-
|
|
478
|
+
const remoteDocument = await (options.documentLoader ?? getDocumentLoader())(keyId);
|
|
479
|
+
document = remoteDocument.document;
|
|
480
|
+
documentUrl = new URL(remoteDocument.documentUrl ?? "", cacheKey);
|
|
333
481
|
} catch (error) {
|
|
334
482
|
return await onFetchError(error, cacheKey, keyId, keyCache, logger);
|
|
335
483
|
}
|
|
336
|
-
return await resolveFetchedKey(document, cacheKey, keyId, cls, options, logger);
|
|
484
|
+
return await resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, options, logger);
|
|
337
485
|
}
|
|
338
486
|
async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
339
487
|
return await fetchKeyWithResult(typeof keyId === "string" ? new URL(keyId) : keyId, cls, options, (_cacheKey, _keyId, _keyCache, _logger) => {
|
|
@@ -359,4 +507,4 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
|
|
|
359
507
|
});
|
|
360
508
|
}
|
|
361
509
|
//#endregion
|
|
362
|
-
export {
|
|
510
|
+
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,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { f as validateCryptoKey, t as doubleKnock } from "./http-C5T-lWqB.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { curry } from "es-toolkit";
|
|
6
6
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
@@ -1,7 +1,7 @@
|
|
|
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-BWNuy1sb.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
6
|
let es_toolkit = require("es-toolkit");
|
|
7
7
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
@@ -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 {
|
|
4
|
+
import { n as version, t as name } from "./deno-CV4wsQzy.mjs";
|
|
5
|
+
import { r as fetchKey, s as validateCryptoKey } from "./key-DiziIx9e.mjs";
|
|
6
6
|
import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
|
|
7
7
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
8
8
|
import { getDocumentLoader } from "@fedify/vocab-runtime";
|
|
@@ -2,10 +2,10 @@ 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-BWNuy1sb.cjs");
|
|
6
|
+
const require_types = require("./types-DWs-qZaY.cjs");
|
|
7
|
+
const require_proof = require("./proof-7T47tIu5.cjs");
|
|
8
|
+
const require_kv_cache = require("./kv-cache-DGPEHc1T.cjs");
|
|
9
9
|
let _logtape_logtape = require("@logtape/logtape");
|
|
10
10
|
let _fedify_vocab = require("@fedify/vocab");
|
|
11
11
|
let _opentelemetry_api = require("@opentelemetry/api");
|
|
@@ -212,7 +212,7 @@ var FederationBuilderImpl = class {
|
|
|
212
212
|
this.collectionTypeIds = {};
|
|
213
213
|
}
|
|
214
214
|
async build(options) {
|
|
215
|
-
const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-
|
|
215
|
+
const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-XNvq3NUZ.cjs"));
|
|
216
216
|
const f = new FederationImpl(options);
|
|
217
217
|
const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
|
|
218
218
|
f.router = this.router.clone();
|
|
@@ -915,6 +915,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
915
915
|
}
|
|
916
916
|
//#endregion
|
|
917
917
|
//#region src/federation/keycache.ts
|
|
918
|
+
const KEY_CACHE_GENERATION = "2";
|
|
918
919
|
var KvKeyCache = class {
|
|
919
920
|
kv;
|
|
920
921
|
prefix;
|
|
@@ -935,13 +936,20 @@ var KvKeyCache = class {
|
|
|
935
936
|
keyId.href
|
|
936
937
|
];
|
|
937
938
|
}
|
|
939
|
+
#entryKey(keyId) {
|
|
940
|
+
return [
|
|
941
|
+
...this.prefix,
|
|
942
|
+
KEY_CACHE_GENERATION,
|
|
943
|
+
keyId.href
|
|
944
|
+
];
|
|
945
|
+
}
|
|
938
946
|
async get(keyId) {
|
|
939
947
|
const negativeExpiration = this.nullKeys.get(keyId.href);
|
|
940
948
|
if (negativeExpiration != null) {
|
|
941
949
|
if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
|
|
942
950
|
this.nullKeys.delete(keyId.href);
|
|
943
951
|
}
|
|
944
|
-
const serialized = await this.kv.get(
|
|
952
|
+
const serialized = await this.kv.get(this.#entryKey(keyId));
|
|
945
953
|
if (serialized === void 0) return void 0;
|
|
946
954
|
if (serialized === null) {
|
|
947
955
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
@@ -953,7 +961,7 @@ var KvKeyCache = class {
|
|
|
953
961
|
try {
|
|
954
962
|
return await _fedify_vocab.Multikey.fromJsonLd(serialized, this.options);
|
|
955
963
|
} catch {
|
|
956
|
-
await this.kv.delete(
|
|
964
|
+
await this.kv.delete(this.#entryKey(keyId));
|
|
957
965
|
return;
|
|
958
966
|
}
|
|
959
967
|
}
|
|
@@ -961,12 +969,12 @@ var KvKeyCache = class {
|
|
|
961
969
|
async set(keyId, key) {
|
|
962
970
|
if (key == null) {
|
|
963
971
|
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
964
|
-
await this.kv.set(
|
|
972
|
+
await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
|
|
965
973
|
return;
|
|
966
974
|
}
|
|
967
975
|
this.nullKeys.delete(keyId.href);
|
|
968
976
|
const serialized = await key.toJsonLd(this.options);
|
|
969
|
-
await this.kv.set(
|
|
977
|
+
await this.kv.set(this.#entryKey(keyId), serialized);
|
|
970
978
|
}
|
|
971
979
|
async getFetchError(keyId) {
|
|
972
980
|
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
|
|
@@ -1687,8 +1695,16 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
1687
1695
|
}
|
|
1688
1696
|
let json;
|
|
1689
1697
|
try {
|
|
1690
|
-
json = await request.clone().
|
|
1698
|
+
json = JSON.parse(await require_types.readBoundedText(request.clone(), require_types.MAX_BODY_SIZE, request.url));
|
|
1691
1699
|
} catch (error) {
|
|
1700
|
+
if (error instanceof require_types.BodyTooLargeError) {
|
|
1701
|
+
request.body?.cancel(error).catch(() => {});
|
|
1702
|
+
span.setStatus({
|
|
1703
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
1704
|
+
message: String(error)
|
|
1705
|
+
});
|
|
1706
|
+
return new Response("Inbox body too large.", { status: 413 });
|
|
1707
|
+
}
|
|
1692
1708
|
logger.error("Failed to parse JSON:\n{error}", {
|
|
1693
1709
|
recipient,
|
|
1694
1710
|
error
|
|
@@ -2705,18 +2721,31 @@ async function readLimitedResponseBody(response, maxBytes) {
|
|
|
2705
2721
|
if (truncated) result += "… (truncated)";
|
|
2706
2722
|
return result;
|
|
2707
2723
|
}
|
|
2708
|
-
async function sendActivityInternal({ activity, activityId, keys, inbox, headers, specDeterminer, tracerProvider }, span) {
|
|
2724
|
+
async function sendActivityInternal({ activity, activityId, keys, inbox, allowPrivateAddress, headers, specDeterminer, tracerProvider }, span) {
|
|
2709
2725
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
2710
2726
|
"fedify",
|
|
2711
2727
|
"federation",
|
|
2712
2728
|
"outbox"
|
|
2713
2729
|
]);
|
|
2730
|
+
async function validateUrl(url) {
|
|
2731
|
+
if (!allowPrivateAddress) try {
|
|
2732
|
+
await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
|
|
2733
|
+
} catch (error) {
|
|
2734
|
+
if (error instanceof _fedify_vocab_runtime.UrlError) logger.error("Disallowed private URL: {url}", {
|
|
2735
|
+
url,
|
|
2736
|
+
error
|
|
2737
|
+
});
|
|
2738
|
+
throw error;
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
await validateUrl(inbox.href);
|
|
2714
2742
|
headers = new Headers(headers);
|
|
2715
2743
|
headers.set("Content-Type", "application/activity+json");
|
|
2716
2744
|
const request = new Request(inbox, {
|
|
2717
2745
|
method: "POST",
|
|
2718
2746
|
headers,
|
|
2719
|
-
body: JSON.stringify(activity)
|
|
2747
|
+
body: JSON.stringify(activity),
|
|
2748
|
+
redirect: "manual"
|
|
2720
2749
|
});
|
|
2721
2750
|
let rsaKey = null;
|
|
2722
2751
|
for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
@@ -2732,9 +2761,10 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, headers
|
|
|
2732
2761
|
});
|
|
2733
2762
|
let response;
|
|
2734
2763
|
try {
|
|
2735
|
-
response = rsaKey == null ? await
|
|
2764
|
+
response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await require_http.doubleKnock(request, rsaKey, {
|
|
2736
2765
|
tracerProvider,
|
|
2737
|
-
specDeterminer
|
|
2766
|
+
specDeterminer,
|
|
2767
|
+
validateRedirect: validateUrl
|
|
2738
2768
|
});
|
|
2739
2769
|
} catch (error) {
|
|
2740
2770
|
logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
|
|
@@ -2766,6 +2796,49 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, headers
|
|
|
2766
2796
|
"activitypub.activity.id": activityId ?? ""
|
|
2767
2797
|
});
|
|
2768
2798
|
}
|
|
2799
|
+
async function fetchWithValidatedRedirects(request, validateUrl) {
|
|
2800
|
+
let body = await request.clone().text();
|
|
2801
|
+
for (let redirects = 0;; redirects++) {
|
|
2802
|
+
const response = await fetch(request, { redirect: "manual" });
|
|
2803
|
+
const location = response.headers.get("Location");
|
|
2804
|
+
if (![
|
|
2805
|
+
301,
|
|
2806
|
+
302,
|
|
2807
|
+
303,
|
|
2808
|
+
307,
|
|
2809
|
+
308
|
|
2810
|
+
].includes(response.status) || location == null) return response;
|
|
2811
|
+
await response.body?.cancel();
|
|
2812
|
+
if (redirects >= 20) throw new TypeError("Too many redirects");
|
|
2813
|
+
const url = new URL(location, request.url);
|
|
2814
|
+
await validateUrl(url.href);
|
|
2815
|
+
const headers = new Headers(request.headers);
|
|
2816
|
+
if (url.origin !== new URL(request.url).origin) {
|
|
2817
|
+
headers.delete("Authorization");
|
|
2818
|
+
headers.delete("Proxy-Authorization");
|
|
2819
|
+
headers.delete("Cookie");
|
|
2820
|
+
headers.delete("Host");
|
|
2821
|
+
}
|
|
2822
|
+
let method = request.method;
|
|
2823
|
+
if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
|
|
2824
|
+
method = "GET";
|
|
2825
|
+
body = void 0;
|
|
2826
|
+
for (const name of [
|
|
2827
|
+
"Content-Encoding",
|
|
2828
|
+
"Content-Language",
|
|
2829
|
+
"Content-Length",
|
|
2830
|
+
"Content-Location",
|
|
2831
|
+
"Content-Type"
|
|
2832
|
+
]) headers.delete(name);
|
|
2833
|
+
}
|
|
2834
|
+
request = new Request(url, {
|
|
2835
|
+
method,
|
|
2836
|
+
headers,
|
|
2837
|
+
body,
|
|
2838
|
+
redirect: "manual"
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2769
2842
|
/**
|
|
2770
2843
|
* An error that is thrown when an activity fails to send to a remote inbox.
|
|
2771
2844
|
* It contains structured information about the failure, including the HTTP
|
|
@@ -3272,6 +3345,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3272
3345
|
}
|
|
3273
3346
|
try {
|
|
3274
3347
|
await sendActivity({
|
|
3348
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
3275
3349
|
keys,
|
|
3276
3350
|
activity: message.activity,
|
|
3277
3351
|
activityId: message.activityId,
|
|
@@ -3666,6 +3740,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3666
3740
|
});
|
|
3667
3741
|
const promises = [];
|
|
3668
3742
|
for (const inbox in inboxes) promises.push(sendActivity({
|
|
3743
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
3669
3744
|
keys,
|
|
3670
3745
|
activity: jsonLd,
|
|
3671
3746
|
activityId: activity.id?.href,
|
|
@@ -4848,6 +4923,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
4848
4923
|
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
4849
4924
|
const promises = [];
|
|
4850
4925
|
for (const inbox in inboxes) promises.push(sendActivity({
|
|
4926
|
+
allowPrivateAddress: ctx.federation.allowPrivateAddress,
|
|
4851
4927
|
keys,
|
|
4852
4928
|
activity: ctx.activity,
|
|
4853
4929
|
activityId: ctx.activityId,
|
|
@@ -2,25 +2,25 @@ import { Temporal } from "@js-temporal/polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { n as RouterError } from "./router-CrMLXoOr.mjs";
|
|
5
|
-
import { n as version, t as name } from "./deno-
|
|
6
|
-
import { t as formatAcceptSignature } from "./accept-
|
|
7
|
-
import {
|
|
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-
|
|
11
|
-
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-
|
|
12
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-
|
|
13
|
-
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-
|
|
14
|
-
import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-
|
|
15
|
-
import { t as getNodeInfo } from "./client-
|
|
16
|
-
import { t as nodeInfoToJson } from "./types-
|
|
17
|
-
import { t as FederationBuilderImpl } from "./builder-
|
|
18
|
-
import { t as buildCollectionSynchronizationHeader } from "./collection-
|
|
19
|
-
import { t as KvKeyCache } from "./keycache-
|
|
20
|
-
import { t as acceptsJsonLd } from "./negotiation-
|
|
21
|
-
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-
|
|
22
|
-
import { t as createExponentialBackoffPolicy } from "./retry-
|
|
23
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-
|
|
5
|
+
import { n as version, t as name } from "./deno-CV4wsQzy.mjs";
|
|
6
|
+
import { t as formatAcceptSignature } from "./accept-Dd__NiUL.mjs";
|
|
7
|
+
import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-DiziIx9e.mjs";
|
|
8
|
+
import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-C9MzFrOj.mjs";
|
|
9
|
+
import { t as getAuthenticatedDocumentLoader } from "./docloader-DV9F08Dn.mjs";
|
|
10
|
+
import { n as kvCache } from "./kv-cache-FsT1ljhs.mjs";
|
|
11
|
+
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-BAeuDKjx.mjs";
|
|
12
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-D08_JUSb.mjs";
|
|
13
|
+
import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-Dc2OXyB-.mjs";
|
|
14
|
+
import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-_S-wFUFM.mjs";
|
|
15
|
+
import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-CQ7zd8tW.mjs";
|
|
16
|
+
import { t as nodeInfoToJson } from "./types-s4mCTGkT.mjs";
|
|
17
|
+
import { t as FederationBuilderImpl } from "./builder-Cwgo1nK5.mjs";
|
|
18
|
+
import { t as buildCollectionSynchronizationHeader } from "./collection-DnoYNwBC.mjs";
|
|
19
|
+
import { t as KvKeyCache } from "./keycache-C1aVprwy.mjs";
|
|
20
|
+
import { t as acceptsJsonLd } from "./negotiation-BHtZN6Y1.mjs";
|
|
21
|
+
import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CX_L28b-.mjs";
|
|
22
|
+
import { t as createExponentialBackoffPolicy } from "./retry-bMXBL97A.mjs";
|
|
23
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-DmcKKipr.mjs";
|
|
24
24
|
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
25
25
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
26
26
|
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
@@ -824,8 +824,16 @@ async function handleInboxInternal(request, parameters, span) {
|
|
|
824
824
|
}
|
|
825
825
|
let json;
|
|
826
826
|
try {
|
|
827
|
-
json = await request.clone().
|
|
827
|
+
json = JSON.parse(await readBoundedText(request.clone(), MAX_BODY_SIZE, request.url));
|
|
828
828
|
} catch (error) {
|
|
829
|
+
if (error instanceof BodyTooLargeError) {
|
|
830
|
+
request.body?.cancel(error).catch(() => {});
|
|
831
|
+
span.setStatus({
|
|
832
|
+
code: SpanStatusCode.ERROR,
|
|
833
|
+
message: String(error)
|
|
834
|
+
});
|
|
835
|
+
return new Response("Inbox body too large.", { status: 413 });
|
|
836
|
+
}
|
|
829
837
|
logger.error("Failed to parse JSON:\n{error}", {
|
|
830
838
|
recipient,
|
|
831
839
|
error
|
|
@@ -2159,6 +2167,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
2159
2167
|
}
|
|
2160
2168
|
try {
|
|
2161
2169
|
await sendActivity({
|
|
2170
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
2162
2171
|
keys,
|
|
2163
2172
|
activity: message.activity,
|
|
2164
2173
|
activityId: message.activityId,
|
|
@@ -2553,6 +2562,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
2553
2562
|
});
|
|
2554
2563
|
const promises = [];
|
|
2555
2564
|
for (const inbox in inboxes) promises.push(sendActivity({
|
|
2565
|
+
allowPrivateAddress: this.allowPrivateAddress,
|
|
2556
2566
|
keys,
|
|
2557
2567
|
activity: jsonLd,
|
|
2558
2568
|
activityId: activity.id?.href,
|
|
@@ -3735,6 +3745,7 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
|
|
|
3735
3745
|
else logger.debug("Forwarding activity immediately without queue since queue is not set.");
|
|
3736
3746
|
const promises = [];
|
|
3737
3747
|
for (const inbox in inboxes) promises.push(sendActivity({
|
|
3748
|
+
allowPrivateAddress: ctx.federation.allowPrivateAddress,
|
|
3738
3749
|
keys,
|
|
3739
3750
|
activity: ctx.activity,
|
|
3740
3751
|
activityId: ctx.activityId,
|