@fedify/fedify 0.8.0-dev.164 → 0.8.0-dev.166
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of @fedify/fedify might be problematic. Click here for more details.
- package/CHANGES.md +28 -1
- package/esm/federation/handler.js +2 -2
- package/esm/federation/middleware.js +12 -7
- package/esm/federation/send.js +2 -2
- package/esm/httpsig/mod.js +32 -14
- package/esm/vocab/lookup.js +4 -1
- package/esm/vocab/vocab.js +552 -352
- package/package.json +1 -1
- package/types/federation/context.d.ts +4 -0
- package/types/federation/context.d.ts.map +1 -1
- package/types/federation/handler.d.ts +2 -1
- package/types/federation/handler.d.ts.map +1 -1
- package/types/federation/middleware.d.ts +7 -1
- package/types/federation/middleware.d.ts.map +1 -1
- package/types/federation/send.d.ts +4 -3
- package/types/federation/send.d.ts.map +1 -1
- package/types/httpsig/mod.d.ts +39 -8
- package/types/httpsig/mod.d.ts.map +1 -1
- package/types/testing/context.d.ts.map +1 -1
- package/types/vocab/lookup.d.ts +8 -0
- package/types/vocab/lookup.d.ts.map +1 -1
- package/types/vocab/vocab.d.ts +306 -86
- package/types/vocab/vocab.d.ts.map +1 -1
package/CHANGES.md
CHANGED
@@ -10,7 +10,13 @@ To be released.
|
|
10
10
|
|
11
11
|
- The CLI toolchain for testing and debugging is now available on JSR:
|
12
12
|
[@fedify/cli]. You can install it with
|
13
|
-
`deno install -A -n fedify
|
13
|
+
`deno install -A --unstable-fs --unstable-kv --unstable-temporal -n fedify
|
14
|
+
jsr:@fedify/cli`, or download a standalone exectuable from the [releases]
|
15
|
+
page.
|
16
|
+
|
17
|
+
- Added `fedify` command.
|
18
|
+
- Added `fedify lookup` subcommand.
|
19
|
+
- Added `fedify inbox` subcommand.
|
14
20
|
|
15
21
|
- Implemented [followers collection synchronization mechanism][FEP-8fcf].
|
16
22
|
|
@@ -40,6 +46,26 @@ To be released.
|
|
40
46
|
parameter became `CollectionDispatcher<Recipient, TContextData, URL>`
|
41
47
|
(was `CollectionDispatcher<Actor | URL, TContextData>`).
|
42
48
|
|
49
|
+
- Some of the responsibility of a document loader was separated to a context
|
50
|
+
loader and a document loader.
|
51
|
+
|
52
|
+
- Added `contextLoader` option to constructors, `fromJsonLd()` static
|
53
|
+
methods, `clone()` methods, and all non-scalar accessors (`get*()`) of
|
54
|
+
Activity Vocabulary classes.
|
55
|
+
- Renamed `documentLoader` option to `contextLoader` in `toJsonLd()`
|
56
|
+
methods of Activity Vocabulary objects.
|
57
|
+
- Added `contextLoader` option to `LookupObjectOptions` interface.
|
58
|
+
- Added `contextLoader` property to `Context` interface.
|
59
|
+
- Added `contextLoader` option to `FederationParameters` interface.
|
60
|
+
- Renamed `documentLoader` option to `contextLoader` in
|
61
|
+
`RespondWithObjectOptions` interface.
|
62
|
+
- Added `GetKeyOwnerOptions` interface.
|
63
|
+
- The type of the second parameter of `getKeyOwner()` function became
|
64
|
+
`GetKeyOwnerOptions` (was `DocumentLoader`).
|
65
|
+
- Added `DoesActorOwnKeyOptions` interface.
|
66
|
+
- The type of the third parameter of `doesActorOwnKey()` function became
|
67
|
+
`DoesActorOwnKeyOptions` (was `DocumentLoader`).
|
68
|
+
|
43
69
|
- Removed the dependency on *@js-temporal/polyfill* on Deno, and Fedify now
|
44
70
|
requires `--unstable-temporal` flag. On other runtime, it still depends
|
45
71
|
on *@js-temporal/polyfill*.
|
@@ -55,6 +81,7 @@ To be released.
|
|
55
81
|
error when the URL redirection was involved in Bun.
|
56
82
|
|
57
83
|
[@fedify/cli]: https://jsr.io/@fedify/cli
|
84
|
+
[releases]: https://github.com/dahlia/fedify/releases
|
58
85
|
[FEP-8fcf]: https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md
|
59
86
|
|
60
87
|
|
@@ -176,7 +176,7 @@ export async function handleInbox(request, { handle, context, kv, kvPrefix, acto
|
|
176
176
|
return await onNotFound(request);
|
177
177
|
}
|
178
178
|
}
|
179
|
-
const key = await verify(request, context.documentLoader);
|
179
|
+
const key = await verify(request, context.documentLoader, context.contextLoader);
|
180
180
|
if (key == null) {
|
181
181
|
logger.error("Failed to verify the request signature.", { handle });
|
182
182
|
const response = new Response("Failed to verify the request signature.", {
|
@@ -233,7 +233,7 @@ export async function handleInbox(request, { handle, context, kv, kvPrefix, acto
|
|
233
233
|
});
|
234
234
|
return response;
|
235
235
|
}
|
236
|
-
if (!await doesActorOwnKey(activity, key, context
|
236
|
+
if (!await doesActorOwnKey(activity, key, context)) {
|
237
237
|
logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", { activity: json, keyId: key.id?.href, actorId: activity.actorId.href });
|
238
238
|
const response = new Response("The signer and the actor do not match.", {
|
239
239
|
status: 401,
|
@@ -32,6 +32,7 @@ export class Federation {
|
|
32
32
|
#inboxListeners;
|
33
33
|
#inboxErrorHandler;
|
34
34
|
#documentLoader;
|
35
|
+
#contextLoader;
|
35
36
|
#authenticatedDocumentLoaderFactory;
|
36
37
|
#treatHttps;
|
37
38
|
#onOutboxError;
|
@@ -40,7 +41,7 @@ export class Federation {
|
|
40
41
|
* Create a new {@link Federation} instance.
|
41
42
|
* @param parameters Parameters for initializing the instance.
|
42
43
|
*/
|
43
|
-
constructor({ kv, kvPrefixes, queue, documentLoader, authenticatedDocumentLoaderFactory, treatHttps, onOutboxError, backoffSchedule, }) {
|
44
|
+
constructor({ kv, kvPrefixes, queue, documentLoader, contextLoader, authenticatedDocumentLoaderFactory, treatHttps, onOutboxError, backoffSchedule, }) {
|
44
45
|
this.#kv = kv;
|
45
46
|
this.#kvPrefixes = {
|
46
47
|
...({
|
@@ -61,6 +62,7 @@ export class Federation {
|
|
61
62
|
kv: kv,
|
62
63
|
prefix: this.#kvPrefixes.remoteDocument,
|
63
64
|
});
|
65
|
+
this.#contextLoader = contextLoader ?? this.#documentLoader;
|
64
66
|
this.#authenticatedDocumentLoaderFactory =
|
65
67
|
authenticatedDocumentLoaderFactory ??
|
66
68
|
getAuthenticatedDocumentLoader;
|
@@ -98,13 +100,14 @@ export class Federation {
|
|
98
100
|
const documentLoader = this.#authenticatedDocumentLoaderFactory({ keyId, privateKey });
|
99
101
|
activity = await Activity.fromJsonLd(message.activity, {
|
100
102
|
documentLoader,
|
103
|
+
contextLoader: this.#contextLoader,
|
101
104
|
});
|
102
105
|
await sendActivity({
|
103
106
|
keyId,
|
104
107
|
privateKey,
|
105
108
|
activity,
|
106
109
|
inbox: new URL(message.inbox),
|
107
|
-
|
110
|
+
contextLoader: this.#contextLoader,
|
108
111
|
headers: new Headers(message.headers),
|
109
112
|
});
|
110
113
|
}
|
@@ -169,6 +172,7 @@ export class Federation {
|
|
169
172
|
const context = {
|
170
173
|
data: contextData,
|
171
174
|
documentLoader: this.#documentLoader,
|
175
|
+
contextLoader: this.#contextLoader,
|
172
176
|
getNodeInfoUri: () => {
|
173
177
|
const path = this.#router.build("nodeInfo", {});
|
174
178
|
if (path == null) {
|
@@ -337,7 +341,7 @@ export class Federation {
|
|
337
341
|
async getSignedKey() {
|
338
342
|
if (signedKey !== undefined)
|
339
343
|
return signedKey;
|
340
|
-
return signedKey = await verify(request, context.documentLoader);
|
344
|
+
return signedKey = await verify(request, context.documentLoader, context.contextLoader);
|
341
345
|
},
|
342
346
|
async getSignedKeyOwner() {
|
343
347
|
if (signedKeyOwner !== undefined)
|
@@ -345,7 +349,7 @@ export class Federation {
|
|
345
349
|
const key = await this.getSignedKey();
|
346
350
|
if (key == null)
|
347
351
|
return signedKeyOwner = null;
|
348
|
-
return signedKeyOwner = await getKeyOwner(key, context
|
352
|
+
return signedKeyOwner = await getKeyOwner(key, context);
|
349
353
|
},
|
350
354
|
};
|
351
355
|
return reqCtx;
|
@@ -698,7 +702,6 @@ export class Federation {
|
|
698
702
|
activityId: activity.id?.href,
|
699
703
|
activity,
|
700
704
|
});
|
701
|
-
const documentLoader = this.#authenticatedDocumentLoaderFactory({ keyId, privateKey });
|
702
705
|
if (immediate || this.#queue == null) {
|
703
706
|
if (immediate) {
|
704
707
|
logger.debug("Sending activity immediately without queue since immediate option " +
|
@@ -714,7 +717,7 @@ export class Federation {
|
|
714
717
|
privateKey,
|
715
718
|
activity,
|
716
719
|
inbox: new URL(inbox),
|
717
|
-
|
720
|
+
contextLoader: this.#contextLoader,
|
718
721
|
headers: collectionSync == null ? undefined : new Headers({
|
719
722
|
"Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
|
720
723
|
}),
|
@@ -725,7 +728,9 @@ export class Federation {
|
|
725
728
|
}
|
726
729
|
logger.debug("Enqueuing activity {activityId} to send later.", { activityId: activity.id?.href, activity });
|
727
730
|
const privateKeyJwk = await exportJwk(privateKey);
|
728
|
-
const activityJson = await activity.toJsonLd({
|
731
|
+
const activityJson = await activity.toJsonLd({
|
732
|
+
contextLoader: this.#contextLoader,
|
733
|
+
});
|
729
734
|
for (const inbox in inboxes) {
|
730
735
|
const message = {
|
731
736
|
type: "outbox",
|
package/esm/federation/send.js
CHANGED
@@ -26,12 +26,12 @@ export function extractInboxes({ recipients, preferSharedInbox }) {
|
|
26
26
|
* See also {@link SendActivityParameters}.
|
27
27
|
* @throws {Error} If the activity fails to send.
|
28
28
|
*/
|
29
|
-
export async function sendActivity({ activity, privateKey, keyId, inbox,
|
29
|
+
export async function sendActivity({ activity, privateKey, keyId, inbox, contextLoader, headers, }) {
|
30
30
|
const logger = getLogger(["fedify", "federation", "outbox"]);
|
31
31
|
if (activity.actorId == null) {
|
32
32
|
throw new TypeError("The activity to send must have at least one actor property.");
|
33
33
|
}
|
34
|
-
const jsonLd = await activity.toJsonLd({
|
34
|
+
const jsonLd = await activity.toJsonLd({ contextLoader });
|
35
35
|
headers = new Headers(headers);
|
36
36
|
headers.set("Content-Type", "application/activity+json");
|
37
37
|
let request = new Request(inbox, {
|
package/esm/httpsig/mod.js
CHANGED
@@ -8,6 +8,7 @@ import * as dntShim from "../_dnt.shims.js";
|
|
8
8
|
import { getLogger } from "@logtape/logtape";
|
9
9
|
import { equals } from "../deps/jsr.io/@std/bytes/0.224.0/mod.js";
|
10
10
|
import { decodeBase64, encodeBase64 } from "../deps/jsr.io/@std/encoding/0.224.0/base64.js";
|
11
|
+
import { fetchDocumentLoader, } from "../runtime/docloader.js";
|
11
12
|
import { isActor } from "../vocab/actor.js";
|
12
13
|
import { CryptographicKey, Object as ASObject, } from "../vocab/vocab.js";
|
13
14
|
import { validateCryptoKey } from "./key.js";
|
@@ -68,12 +69,13 @@ const supportedHashAlgorithms = {
|
|
68
69
|
* under the hood.
|
69
70
|
* @param request The request to verify.
|
70
71
|
* @param documentLoader The document loader to use for fetching the public key.
|
72
|
+
* @param contextLoader The context loader to use for JSON-LD context retrieval.
|
71
73
|
* @param currentTime The current time. If not specified, the current time is
|
72
74
|
* used. This is useful for testing.
|
73
75
|
* @returns The public key of the verified signature, or `null` if the signature
|
74
76
|
* could not be verified.
|
75
77
|
*/
|
76
|
-
export async function verify(request, documentLoader, currentTime) {
|
78
|
+
export async function verify(request, documentLoader, contextLoader, currentTime) {
|
77
79
|
const logger = getLogger(["fedify", "httpsig", "verify"]);
|
78
80
|
request = request.clone();
|
79
81
|
const dateHeader = request.headers.get("Date");
|
@@ -159,13 +161,19 @@ export async function verify(request, documentLoader, currentTime) {
|
|
159
161
|
}
|
160
162
|
let object;
|
161
163
|
try {
|
162
|
-
object = await ASObject.fromJsonLd(document, {
|
164
|
+
object = await ASObject.fromJsonLd(document, {
|
165
|
+
documentLoader,
|
166
|
+
contextLoader,
|
167
|
+
});
|
163
168
|
}
|
164
169
|
catch (e) {
|
165
170
|
if (!(e instanceof TypeError))
|
166
171
|
throw e;
|
167
172
|
try {
|
168
|
-
object = await CryptographicKey.fromJsonLd(document, {
|
173
|
+
object = await CryptographicKey.fromJsonLd(document, {
|
174
|
+
documentLoader,
|
175
|
+
contextLoader,
|
176
|
+
});
|
169
177
|
}
|
170
178
|
catch (e) {
|
171
179
|
if (e instanceof TypeError) {
|
@@ -179,7 +187,7 @@ export async function verify(request, documentLoader, currentTime) {
|
|
179
187
|
if (object instanceof CryptographicKey)
|
180
188
|
key = object;
|
181
189
|
else if (isActor(object)) {
|
182
|
-
for await (const k of object.getPublicKeys({ documentLoader })) {
|
190
|
+
for await (const k of object.getPublicKeys({ documentLoader, contextLoader })) {
|
183
191
|
if (k.id?.href === keyId) {
|
184
192
|
key = k;
|
185
193
|
break;
|
@@ -231,14 +239,14 @@ export async function verify(request, documentLoader, currentTime) {
|
|
231
239
|
* Checks if the actor of the given activity owns the specified key.
|
232
240
|
* @param activity The activity to check.
|
233
241
|
* @param key The public key to check.
|
234
|
-
* @param
|
242
|
+
* @param options Options for checking the key ownership.
|
235
243
|
* @returns Whether the actor is the owner of the key.
|
236
244
|
*/
|
237
|
-
export async function doesActorOwnKey(activity, key,
|
245
|
+
export async function doesActorOwnKey(activity, key, options) {
|
238
246
|
if (key.ownerId != null) {
|
239
247
|
return key.ownerId.href === activity.actorId?.href;
|
240
248
|
}
|
241
|
-
const actor = await activity.getActor(
|
249
|
+
const actor = await activity.getActor(options);
|
242
250
|
if (actor == null || !isActor(actor))
|
243
251
|
return false;
|
244
252
|
for (const publicKeyId of actor.publicKeyIds) {
|
@@ -248,14 +256,18 @@ export async function doesActorOwnKey(activity, key, documentLoader) {
|
|
248
256
|
return false;
|
249
257
|
}
|
250
258
|
/**
|
251
|
-
* Gets the actor that owns the specified key. Returns `null` if the key has no
|
259
|
+
* Gets the actor that owns the specified key. Returns `null` if the key has no
|
260
|
+
* known owner.
|
252
261
|
*
|
253
262
|
* @param keyId The ID of the key to check, or the key itself.
|
254
|
-
* @param
|
255
|
-
* @returns The actor that owns the key, or `null` if the key has no known
|
263
|
+
* @param options Options for getting the key owner.
|
264
|
+
* @returns The actor that owns the key, or `null` if the key has no known
|
265
|
+
* owner.
|
256
266
|
* @since 0.7.0
|
257
267
|
*/
|
258
|
-
export async function getKeyOwner(keyId,
|
268
|
+
export async function getKeyOwner(keyId, options) {
|
269
|
+
const documentLoader = options.documentLoader ?? fetchDocumentLoader;
|
270
|
+
const contextLoader = options.contextLoader ?? fetchDocumentLoader;
|
259
271
|
let object;
|
260
272
|
if (keyId instanceof CryptographicKey) {
|
261
273
|
object = keyId;
|
@@ -273,13 +285,19 @@ export async function getKeyOwner(keyId, documentLoader) {
|
|
273
285
|
return null;
|
274
286
|
}
|
275
287
|
try {
|
276
|
-
object = await ASObject.fromJsonLd(keyDoc, {
|
288
|
+
object = await ASObject.fromJsonLd(keyDoc, {
|
289
|
+
documentLoader,
|
290
|
+
contextLoader,
|
291
|
+
});
|
277
292
|
}
|
278
293
|
catch (e) {
|
279
294
|
if (!(e instanceof TypeError))
|
280
295
|
throw e;
|
281
296
|
try {
|
282
|
-
object = await CryptographicKey.fromJsonLd(keyDoc, {
|
297
|
+
object = await CryptographicKey.fromJsonLd(keyDoc, {
|
298
|
+
documentLoader,
|
299
|
+
contextLoader,
|
300
|
+
});
|
283
301
|
}
|
284
302
|
catch (e) {
|
285
303
|
if (e instanceof TypeError)
|
@@ -292,7 +310,7 @@ export async function getKeyOwner(keyId, documentLoader) {
|
|
292
310
|
if (object instanceof CryptographicKey) {
|
293
311
|
if (object.ownerId == null)
|
294
312
|
return null;
|
295
|
-
owner = await object.getOwner({ documentLoader });
|
313
|
+
owner = await object.getOwner({ documentLoader, contextLoader });
|
296
314
|
}
|
297
315
|
else if (isActor(object)) {
|
298
316
|
owner = object;
|
package/esm/vocab/lookup.js
CHANGED
@@ -73,7 +73,10 @@ export async function lookupObject(identifier, options = {}) {
|
|
73
73
|
if (document == null)
|
74
74
|
return null;
|
75
75
|
try {
|
76
|
-
return await Object.fromJsonLd(document, {
|
76
|
+
return await Object.fromJsonLd(document, {
|
77
|
+
documentLoader,
|
78
|
+
contextLoader: options.contextLoader,
|
79
|
+
});
|
77
80
|
}
|
78
81
|
catch (e) {
|
79
82
|
if (e instanceof TypeError)
|