@fedify/fedify 0.8.0-dev.164 → 0.8.0-dev.167
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.
Potentially problematic release.
This version of @fedify/fedify might be problematic. Click here for more details.
- package/CHANGES.md +33 -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 +6 -2
- 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*.
|
@@ -54,7 +80,13 @@ To be released.
|
|
54
80
|
- Fixed a bug where the authenticated document loader had thrown `InvalidUrl`
|
55
81
|
error when the URL redirection was involved in Bun.
|
56
82
|
|
83
|
+
- Fixed a bug of `lookupObject()` that it had failed to look up the actor
|
84
|
+
object when WebFinger response had no links with
|
85
|
+
`"type": "application/activity+json"` but had `"type":
|
86
|
+
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""`.
|
87
|
+
|
57
88
|
[@fedify/cli]: https://jsr.io/@fedify/cli
|
89
|
+
[releases]: https://github.com/dahlia/fedify/releases
|
58
90
|
[FEP-8fcf]: https://codeberg.org/fediverse/fep/src/branch/main/fep/8fcf/fep-8fcf.md
|
59
91
|
|
60
92
|
|
@@ -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
@@ -57,7 +57,8 @@ export async function lookupObject(identifier, options = {}) {
|
|
57
57
|
if (jrd?.links == null)
|
58
58
|
return null;
|
59
59
|
for (const l of jrd.links) {
|
60
|
-
if (l.type !== "application/activity+json"
|
60
|
+
if (l.type !== "application/activity+json" &&
|
61
|
+
!l.type?.match(/application\/ld\+json;\s*profile="https:\/\/www.w3.org\/ns\/activitystreams"/) || l.rel !== "self")
|
61
62
|
continue;
|
62
63
|
try {
|
63
64
|
const remoteDoc = await documentLoader(l.href);
|
@@ -73,7 +74,10 @@ export async function lookupObject(identifier, options = {}) {
|
|
73
74
|
if (document == null)
|
74
75
|
return null;
|
75
76
|
try {
|
76
|
-
return await Object.fromJsonLd(document, {
|
77
|
+
return await Object.fromJsonLd(document, {
|
78
|
+
documentLoader,
|
79
|
+
contextLoader: options.contextLoader,
|
80
|
+
});
|
77
81
|
}
|
78
82
|
catch (e) {
|
79
83
|
if (e instanceof TypeError)
|