@fedify/fedify 1.0.0-dev.397 → 1.0.0-dev.399

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/CHANGES.md CHANGED
@@ -28,6 +28,21 @@ To be released.
28
28
  - Added `attachSignature()` function.
29
29
  - Added `detachSignature()` function.
30
30
 
31
+ - In inbox listeners, a received activity now can be forwarded to another
32
+ server. [[#137]]
33
+
34
+ - Added `InboxContext` interface.
35
+ - Added `ForwardActivityOptions` interface.
36
+ - The first parameter of the `InboxListener` callback type became
37
+ `InboxContext` (was `Context`).
38
+
39
+ - The `Context.parseUri()` method's parameter type became `URL | null`
40
+ (was `URL`).
41
+
42
+ - `Context.sendActivity()` method now adds Object Integrity Proofs to
43
+ the activity to be sent only once. It had added Object Integrity Proofs
44
+ to the activity for every recipient before.
45
+
31
46
  - WebFinger responses now include <http://webfinger.net/rel/avatar> links
32
47
  if the `Actor` object returned by the actor dispatcher has `icon`/`icons`
33
48
  property.
@@ -43,6 +58,7 @@ To be released.
43
58
 
44
59
  [Linked Data Signatures]: https://web.archive.org/web/20170923124140/https://w3c-dvcg.github.io/ld-signatures/
45
60
  [#135]: https://github.com/dahlia/fedify/issues/135
61
+ [#137]: https://github.com/dahlia/fedify/issues/137
46
62
 
47
63
 
48
64
  Version 0.15.1
@@ -0,0 +1 @@
1
+ export {};
@@ -175,7 +175,7 @@ function filterCollectionItems(items, collectionName, filterPredicate) {
175
175
  }
176
176
  return result;
177
177
  }
178
- export async function handleInbox(request, { handle, context, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, }) {
178
+ export async function handleInbox(request, { handle, context, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, }) {
179
179
  const logger = getLogger(["fedify", "federation", "inbox"]);
180
180
  if (actorDispatcher == null) {
181
181
  logger.error("Actor dispatcher is not set.", { handle });
@@ -343,7 +343,7 @@ export async function handleInbox(request, { handle, context, kv, kvPrefixes, qu
343
343
  attempt: 0,
344
344
  started: new Date().toISOString(),
345
345
  });
346
- logger.info("Activity {activityId} is eunqueued.", { activityId: activity.id?.href, activity: json });
346
+ logger.info("Activity {activityId} is enqueued.", { activityId: activity.id?.href, activity: json });
347
347
  return new Response("Activity is enqueued.", {
348
348
  status: 202,
349
349
  headers: { "Content-Type": "text/plain; charset=utf-8" },
@@ -358,7 +358,7 @@ export async function handleInbox(request, { handle, context, kv, kvPrefixes, qu
358
358
  });
359
359
  }
360
360
  try {
361
- await listener(context, activity);
361
+ await listener(inboxContextFactory(json), activity);
362
362
  }
363
363
  catch (error) {
364
364
  try {
@@ -4,7 +4,9 @@ import { handleNodeInfo, handleNodeInfoJrd } from "../nodeinfo/handler.js";
4
4
  import { fetchDocumentLoader, getAuthenticatedDocumentLoader, kvCache, } from "../runtime/docloader.js";
5
5
  import { verifyRequest } from "../sig/http.js";
6
6
  import { exportJwk, importJwk, validateCryptoKey } from "../sig/key.js";
7
+ import { hasSignature, signJsonLd } from "../sig/ld.js";
7
8
  import { getKeyOwner } from "../sig/owner.js";
9
+ import { signObject } from "../sig/proof.js";
8
10
  import { lookupObject } from "../vocab/lookup.js";
9
11
  import { Activity, CryptographicKey, Multikey, } from "../vocab/vocab.js";
10
12
  import { handleWebFinger } from "../webfinger/handler.js";
@@ -23,7 +25,7 @@ import { extractInboxes, sendActivity } from "./send.js";
23
25
  export function createFederation(options) {
24
26
  return new FederationImpl(options);
25
27
  }
26
- class FederationImpl {
28
+ export class FederationImpl {
27
29
  kv;
28
30
  kvPrefixes;
29
31
  queue;
@@ -128,45 +130,44 @@ class FederationImpl {
128
130
  keyIds: message.keys.map((pair) => pair.keyId),
129
131
  inbox: message.inbox,
130
132
  activity: message.activity,
133
+ activityId: message.activityId,
131
134
  attempt: message.attempt,
132
135
  headers: message.headers,
133
136
  };
134
- let activity = null;
135
- try {
136
- const keys = [];
137
- let rsaKeyPair = null;
138
- for (const { keyId, privateKey } of message.keys) {
139
- const pair = {
140
- keyId: new URL(keyId),
141
- privateKey: await importJwk(privateKey, "private"),
142
- };
143
- if (rsaKeyPair == null &&
144
- pair.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
145
- rsaKeyPair = pair;
146
- }
147
- keys.push(pair);
137
+ const keys = [];
138
+ let rsaKeyPair = null;
139
+ for (const { keyId, privateKey } of message.keys) {
140
+ const pair = {
141
+ keyId: new URL(keyId),
142
+ privateKey: await importJwk(privateKey, "private"),
143
+ };
144
+ if (rsaKeyPair == null &&
145
+ pair.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
146
+ rsaKeyPair = pair;
148
147
  }
149
- const documentLoader = rsaKeyPair == null
150
- ? this.documentLoader
151
- : this.authenticatedDocumentLoaderFactory(rsaKeyPair);
152
- activity = await Activity.fromJsonLd(message.activity, {
153
- documentLoader,
154
- contextLoader: this.contextLoader,
155
- });
148
+ keys.push(pair);
149
+ }
150
+ try {
156
151
  await sendActivity({
157
152
  keys,
158
- activity,
153
+ activity: message.activity,
154
+ activityId: message.activityId,
159
155
  inbox: new URL(message.inbox),
160
- contextLoader: this.contextLoader,
161
156
  headers: new Headers(message.headers),
162
157
  });
163
158
  }
164
159
  catch (error) {
160
+ const activity = await Activity.fromJsonLd(message.activity, {
161
+ contextLoader: this.contextLoader,
162
+ documentLoader: rsaKeyPair == null
163
+ ? this.documentLoader
164
+ : this.authenticatedDocumentLoaderFactory(rsaKeyPair),
165
+ });
165
166
  try {
166
167
  this.onOutboxError?.(error, activity);
167
168
  }
168
169
  catch (error) {
169
- logger.error("An unexpected error occurred in onError handler:\n{error}", { ...logData, error, activityId: activity?.id?.href });
170
+ logger.error("An unexpected error occurred in onError handler:\n{error}", { ...logData, error });
170
171
  }
171
172
  const delay = this.outboxRetryPolicy({
172
173
  elapsedTime: dntShim.Temporal.Instant.from(message.started).until(dntShim.Temporal.Now.instant()),
@@ -174,7 +175,7 @@ class FederationImpl {
174
175
  });
175
176
  if (delay != null) {
176
177
  logger.error("Failed to send activity {activityId} to {inbox} (attempt " +
177
- "#{attempt}); retry...:\n{error}", { ...logData, error, activityId: activity?.id?.href });
178
+ "#{attempt}); retry...:\n{error}", { ...logData, error });
178
179
  this.queue?.enqueue({
179
180
  ...message,
180
181
  attempt: message.attempt + 1,
@@ -186,11 +187,11 @@ class FederationImpl {
186
187
  }
187
188
  else {
188
189
  logger.error("Failed to send activity {activityId} to {inbox} after {attempt} " +
189
- "attempts; giving up:\n{error}", { ...logData, error, activityId: activity?.id?.href });
190
+ "attempts; giving up:\n{error}", { ...logData, error });
190
191
  }
191
192
  return;
192
193
  }
193
- logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData, activityId: activity?.id?.href });
194
+ logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
194
195
  }
195
196
  async #listenInboxMessage(ctxData, message) {
196
197
  const logger = getLogger(["fedify", "federation", "inbox"]);
@@ -234,7 +235,7 @@ class FederationImpl {
234
235
  return;
235
236
  }
236
237
  try {
237
- await listener(context, activity);
238
+ await listener(context.toInboxContext(message.activity), activity);
238
239
  }
239
240
  catch (error) {
240
241
  try {
@@ -782,8 +783,6 @@ class FederationImpl {
782
783
  logger.error("Activity {activityId} to send does not have an actor.", { activity, activityId: activity?.id?.href });
783
784
  throw new TypeError("The activity to send must have at least one actor property.");
784
785
  }
785
- if (!this.manuallyStartQueue)
786
- this.#startQueue(contextData);
787
786
  if (activity.id == null) {
788
787
  activity = activity.clone({
789
788
  id: new URL(`urn:uuid:${dntShim.crypto.randomUUID()}`),
@@ -799,21 +798,78 @@ class FederationImpl {
799
798
  activityId: activity.id?.href,
800
799
  activity,
801
800
  });
801
+ if (activity.id == null) {
802
+ throw new TypeError("The activity to send must have an id.");
803
+ }
804
+ if (activity.actorId == null) {
805
+ throw new TypeError("The activity to send must have at least one actor property.");
806
+ }
807
+ else if (keys.length < 1) {
808
+ throw new TypeError("The keys must not be empty.");
809
+ }
810
+ const activityId = activity.id.href;
811
+ let proofCreated = false;
812
+ let rsaKey = null;
813
+ for (const { keyId, privateKey } of keys) {
814
+ validateCryptoKey(privateKey, "private");
815
+ if (rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
816
+ rsaKey = { keyId, privateKey };
817
+ continue;
818
+ }
819
+ if (privateKey.algorithm.name === "Ed25519") {
820
+ activity = await signObject(activity, privateKey, keyId, {
821
+ contextLoader: this.contextLoader,
822
+ });
823
+ proofCreated = true;
824
+ }
825
+ }
826
+ let jsonLd = await activity.toJsonLd({
827
+ format: "compact",
828
+ contextLoader: this.contextLoader,
829
+ });
830
+ if (rsaKey == null) {
831
+ logger.warn("No supported key found to create a Linked Data signature for " +
832
+ "the activity {activityId}. The activity will be sent without " +
833
+ "a Linked Data signature. In order to create a Linked Data " +
834
+ "signature, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
835
+ activityId,
836
+ keys: keys.map((pair) => ({
837
+ keyId: pair.keyId.href,
838
+ privateKey: pair.privateKey,
839
+ })),
840
+ });
841
+ }
842
+ else {
843
+ jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
844
+ contextLoader: this.contextLoader,
845
+ });
846
+ }
847
+ if (!proofCreated) {
848
+ logger.warn("No supported key found to create a proof for the activity {activityId}. " +
849
+ "The activity will be sent without a proof. " +
850
+ "In order to create a proof, at least one Ed25519 key must be provided.", {
851
+ activityId,
852
+ keys: keys.map((pair) => ({
853
+ keyId: pair.keyId.href,
854
+ privateKey: pair.privateKey,
855
+ })),
856
+ });
857
+ }
802
858
  if (immediate || this.queue == null) {
803
859
  if (immediate) {
804
860
  logger.debug("Sending activity immediately without queue since immediate option " +
805
- "is set.", { activityId: activity.id?.href, activity });
861
+ "is set.", { activityId: activity.id.href, activity: jsonLd });
806
862
  }
807
863
  else {
808
- logger.debug("Sending activity immediately without queue since queue is not set.", { activityId: activity.id?.href, activity });
864
+ logger.debug("Sending activity immediately without queue since queue is not set.", { activityId: activity.id.href, activity: jsonLd });
809
865
  }
810
866
  const promises = [];
811
867
  for (const inbox in inboxes) {
812
868
  promises.push(sendActivity({
813
869
  keys,
814
- activity,
870
+ activity: jsonLd,
871
+ activityId: activity.id?.href,
815
872
  inbox: new URL(inbox),
816
- contextLoader: this.contextLoader,
817
873
  headers: collectionSync == null ? undefined : new Headers({
818
874
  "Collection-Synchronization": await buildCollectionSynchronizationHeader(collectionSync, inboxes[inbox]),
819
875
  }),
@@ -822,20 +878,20 @@ class FederationImpl {
822
878
  await Promise.all(promises);
823
879
  return;
824
880
  }
825
- logger.debug("Enqueuing activity {activityId} to send later.", { activityId: activity.id?.href, activity });
881
+ logger.debug("Enqueuing activity {activityId} to send later.", { activityId: activity.id.href, activity: jsonLd });
826
882
  const keyJwkPairs = [];
827
883
  for (const { keyId, privateKey } of keys) {
828
884
  const privateKeyJwk = await exportJwk(privateKey);
829
885
  keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
830
886
  }
831
- const activityJson = await activity.toJsonLd({
832
- contextLoader: this.contextLoader,
833
- });
887
+ if (!this.manuallyStartQueue)
888
+ this.#startQueue(contextData);
834
889
  for (const inbox in inboxes) {
835
890
  const message = {
836
891
  type: "outbox",
837
892
  keys: keyJwkPairs,
838
- activity: activityJson,
893
+ activity: jsonLd,
894
+ activityId: activity.id?.href,
839
895
  inbox,
840
896
  started: new Date().toISOString(),
841
897
  attempt: 0,
@@ -968,6 +1024,7 @@ class FederationImpl {
968
1024
  return await handleInbox(request, {
969
1025
  handle: route.values.handle ?? null,
970
1026
  context,
1027
+ inboxContextFactory: context.toInboxContext.bind(context),
971
1028
  kv: this.kv,
972
1029
  kvPrefixes: this.kvPrefixes,
973
1030
  queue: this.queue,
@@ -1064,6 +1121,15 @@ class ContextImpl {
1064
1121
  this.invokedFromActorKeyPairsDispatcher =
1065
1122
  invokedFromActorKeyPairsDispatcher;
1066
1123
  }
1124
+ toInboxContext(activity) {
1125
+ return new InboxContextImpl(activity, {
1126
+ url: this.url,
1127
+ federation: this.federation,
1128
+ data: this.data,
1129
+ documentLoader: this.documentLoader,
1130
+ invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher,
1131
+ });
1132
+ }
1067
1133
  get hostname() {
1068
1134
  return this.url.hostname;
1069
1135
  }
@@ -1165,6 +1231,8 @@ class ContextImpl {
1165
1231
  return new URL(path, this.url);
1166
1232
  }
1167
1233
  parseUri(uri) {
1234
+ if (uri == null)
1235
+ return null;
1168
1236
  if (uri.origin !== this.url.origin)
1169
1237
  return null;
1170
1238
  const route = this.federation.router.route(uri.pathname);
@@ -1336,7 +1404,7 @@ class ContextImpl {
1336
1404
  throw new Error("If recipients is 'followers', sender must be an actor handle.");
1337
1405
  }
1338
1406
  expandedRecipients = [];
1339
- for await (const recipient of this.#getFollowers(sender.handle)) {
1407
+ for await (const recipient of this.getFollowers(sender.handle)) {
1340
1408
  expandedRecipients.push(recipient);
1341
1409
  }
1342
1410
  const collectionId = this.federation.router.build("followers", sender);
@@ -1349,7 +1417,7 @@ class ContextImpl {
1349
1417
  }
1350
1418
  return await this.federation.sendActivity(keys, expandedRecipients, activity, opts);
1351
1419
  }
1352
- async *#getFollowers(handle) {
1420
+ async *getFollowers(handle) {
1353
1421
  if (this.federation.followersCallbacks == null) {
1354
1422
  throw new Error("No followers collection dispatcher registered.");
1355
1423
  }
@@ -1450,6 +1518,120 @@ class RequestContextImpl extends ContextImpl {
1450
1518
  return this.#signedKeyOwner = await getKeyOwner(key, this);
1451
1519
  }
1452
1520
  }
1521
+ export class InboxContextImpl extends ContextImpl {
1522
+ activity;
1523
+ constructor(activity, options) {
1524
+ super(options);
1525
+ this.activity = activity;
1526
+ }
1527
+ async forwardActivity(forwarder, recipients, options) {
1528
+ const logger = getLogger(["fedify", "federation", "inbox"]);
1529
+ let keys;
1530
+ if ("handle" in forwarder) {
1531
+ keys = await this.getKeyPairsFromHandle(forwarder.handle);
1532
+ if (keys.length < 1) {
1533
+ throw new Error(`No key pair found for actor ${JSON.stringify(forwarder.handle)}.`);
1534
+ }
1535
+ }
1536
+ else if (Array.isArray(forwarder)) {
1537
+ if (forwarder.length < 1) {
1538
+ throw new Error("The forwarder's key pairs are empty.");
1539
+ }
1540
+ keys = forwarder;
1541
+ }
1542
+ else {
1543
+ keys = [forwarder];
1544
+ }
1545
+ let activityId = undefined;
1546
+ if (!hasSignature(this.activity)) {
1547
+ let hasProof;
1548
+ try {
1549
+ const activity = await Activity.fromJsonLd(this.activity, this);
1550
+ activityId = activity.id?.href;
1551
+ hasProof = await activity.getProof() != null;
1552
+ }
1553
+ catch {
1554
+ hasProof = false;
1555
+ }
1556
+ if (!hasProof) {
1557
+ if (options?.skipIfUnsigned)
1558
+ return;
1559
+ logger.warn("The received activity {activityId} is not signed; even if it is " +
1560
+ "forwarded to other servers as is, it may not be accepted by " +
1561
+ "them due to the lack of a signature/proof.");
1562
+ }
1563
+ }
1564
+ if (activityId == null && typeof this.activity === "object" &&
1565
+ this.activity != null) {
1566
+ activityId =
1567
+ "@id" in this.activity && typeof this.activity["@id"] === "string"
1568
+ ? this.activity["@id"]
1569
+ : "id" in this.activity && typeof this.activity.id === "string"
1570
+ ? this.activity.id
1571
+ : undefined;
1572
+ }
1573
+ if (recipients === "followers") {
1574
+ if (!("handle" in forwarder)) {
1575
+ throw new Error("If recipients is 'followers', forwarder must be an actor handle.");
1576
+ }
1577
+ const followers = [];
1578
+ for await (const recipient of this.getFollowers(forwarder.handle)) {
1579
+ followers.push(recipient);
1580
+ }
1581
+ recipients = followers;
1582
+ }
1583
+ const inboxes = extractInboxes({
1584
+ recipients: Array.isArray(recipients) ? recipients : [recipients],
1585
+ preferSharedInbox: options?.preferSharedInbox,
1586
+ excludeBaseUris: options?.excludeBaseUris,
1587
+ });
1588
+ logger.debug("Forwarding activity {activityId} to inboxes:\n{inboxes}", {
1589
+ inboxes: globalThis.Object.keys(inboxes),
1590
+ activityId,
1591
+ activity: this.activity,
1592
+ });
1593
+ if (options?.immediate || this.federation.queue == null) {
1594
+ if (options?.immediate) {
1595
+ logger.debug("Forwarding activity immediately without queue since immediate " +
1596
+ "option is set.");
1597
+ }
1598
+ else {
1599
+ logger.debug("Forwarding activity immediately without queue since queue is not " +
1600
+ "set.");
1601
+ }
1602
+ const promises = [];
1603
+ for (const inbox in inboxes) {
1604
+ promises.push(sendActivity({
1605
+ keys,
1606
+ activity: this.activity,
1607
+ activityId: activityId,
1608
+ inbox: new URL(inbox),
1609
+ }));
1610
+ }
1611
+ await Promise.all(promises);
1612
+ return;
1613
+ }
1614
+ logger.debug("Enqueuing activity {activityId} to forward later.", { activityId, activity: this.activity });
1615
+ const keyJwkPairs = [];
1616
+ for (const { keyId, privateKey } of keys) {
1617
+ const privateKeyJwk = await exportJwk(privateKey);
1618
+ keyJwkPairs.push({ keyId: keyId.href, privateKey: privateKeyJwk });
1619
+ }
1620
+ for (const inbox in inboxes) {
1621
+ const message = {
1622
+ type: "outbox",
1623
+ keys: keyJwkPairs,
1624
+ activity: this.activity,
1625
+ activityId,
1626
+ inbox,
1627
+ started: new Date().toISOString(),
1628
+ attempt: 0,
1629
+ headers: {},
1630
+ };
1631
+ this.federation.queue.enqueue(message);
1632
+ }
1633
+ }
1634
+ }
1453
1635
  function notFound(_request) {
1454
1636
  return new Response("Not Found", { status: 404 });
1455
1637
  }
@@ -6,9 +6,10 @@
6
6
  export * from "./callback.js";
7
7
  export * from "./collection.js";
8
8
  export * from "./context.js";
9
+ export * from "./federation.js";
9
10
  export { respondWithObject, respondWithObjectIfAcceptable, } from "./handler.js";
10
11
  export * from "./kv.js";
11
- export * from "./middleware.js";
12
+ export { createFederation, } from "./middleware.js";
12
13
  export * from "./mq.js";
13
14
  export * from "./retry.js";
14
15
  export * from "./router.js";
@@ -1,8 +1,5 @@
1
1
  import { getLogger } from "@logtape/logtape";
2
2
  import { signRequest } from "../sig/http.js";
3
- import { validateCryptoKey } from "../sig/key.js";
4
- import { signJsonLd } from "../sig/ld.js";
5
- import { signObject } from "../sig/proof.js";
6
3
  /**
7
4
  * Extracts the inbox URLs from recipients.
8
5
  * @param parameters The parameters to extract the inboxes.
@@ -33,73 +30,22 @@ export function extractInboxes({ recipients, preferSharedInbox, excludeBaseUris
33
30
  * See also {@link SendActivityParameters}.
34
31
  * @throws {Error} If the activity fails to send.
35
32
  */
36
- export async function sendActivity({ activity, keys, inbox, contextLoader, documentLoader, headers, }) {
33
+ export async function sendActivity({ activity, activityId, keys, inbox, headers, }) {
37
34
  const logger = getLogger(["fedify", "federation", "outbox"]);
38
- if (activity.id == null) {
39
- throw new TypeError("The activity to send must have an id.");
40
- }
41
- if (activity.actorId == null) {
42
- throw new TypeError("The activity to send must have at least one actor property.");
43
- }
44
- else if (keys.length < 1) {
45
- throw new TypeError("The keys must not be empty.");
46
- }
47
- const activityId = activity.id.href;
48
- let proofCreated = false;
49
- let rsaKey = null;
50
- for (const { keyId, privateKey } of keys) {
51
- validateCryptoKey(privateKey, "private");
52
- if (rsaKey == null && privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
53
- rsaKey = { keyId, privateKey };
54
- continue;
55
- }
56
- if (privateKey.algorithm.name === "Ed25519") {
57
- activity = await signObject(activity, privateKey, keyId, {
58
- documentLoader,
59
- contextLoader,
60
- });
61
- proofCreated = true;
62
- }
63
- }
64
- let jsonLd = await activity.toJsonLd({
65
- format: "compact",
66
- contextLoader,
67
- });
68
- if (rsaKey == null) {
69
- logger.warn("No supported key found to create a Linked Data signature for " +
70
- "the activity {activityId}. The activity will be sent without " +
71
- "a Linked Data signature. In order to create a Linked Data " +
72
- "signature, at least one RSASSA-PKCS1-v1_5 key must be provided.", {
73
- activityId,
74
- keys: keys.map((pair) => ({
75
- keyId: pair.keyId.href,
76
- privateKey: pair.privateKey,
77
- })),
78
- });
79
- }
80
- else {
81
- jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
82
- contextLoader,
83
- });
84
- }
85
- if (!proofCreated) {
86
- logger.warn("No supported key found to create a proof for the activity {activityId}. " +
87
- "The activity will be sent without a proof. " +
88
- "In order to create a proof, at least one Ed25519 key must be provided.", {
89
- activityId,
90
- keys: keys.map((pair) => ({
91
- keyId: pair.keyId.href,
92
- privateKey: pair.privateKey,
93
- })),
94
- });
95
- }
96
35
  headers = new Headers(headers);
97
36
  headers.set("Content-Type", "application/activity+json");
98
37
  let request = new Request(inbox, {
99
38
  method: "POST",
100
39
  headers,
101
- body: JSON.stringify(jsonLd),
40
+ body: JSON.stringify(activity),
102
41
  });
42
+ let rsaKey = null;
43
+ for (const key of keys) {
44
+ if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
45
+ rsaKey = key;
46
+ break;
47
+ }
48
+ }
103
49
  if (rsaKey == null) {
104
50
  logger.warn("No supported key found to sign the request to {inbox}. " +
105
51
  "The request will be sent without a signature. " +
package/esm/sig/ld.js CHANGED
@@ -73,7 +73,13 @@ export async function signJsonLd(jsonLd, privateKey, keyId, options) {
73
73
  const signature = await createSignature(jsonLd, privateKey, keyId, options);
74
74
  return attachSignature(jsonLd, signature);
75
75
  }
76
- function hasSignature(jsonLd) {
76
+ /**
77
+ * Checks if the given JSON-LD document has a Linked Data Signature.
78
+ * @param jsonLd The JSON-LD document to check.
79
+ * @returns `true` if the document has a signature; `false` otherwise.
80
+ * @since 1.0.0
81
+ */
82
+ export function hasSignature(jsonLd) {
77
83
  if (typeof jsonLd !== "object" || jsonLd == null)
78
84
  return false;
79
85
  if ("signature" in jsonLd) {
package/esm/sig/mod.js CHANGED
@@ -5,6 +5,6 @@
5
5
  */
6
6
  export { signRequest, verifyRequest, } from "./http.js";
7
7
  export { exportJwk, generateCryptoKeyPair, importJwk, } from "./key.js";
8
- export * from "./ld.js";
8
+ export { attachSignature, createSignature, detachSignature, signJsonLd, verifyJsonLd, verifySignature, } from "./ld.js";
9
9
  export { doesActorOwnKey, getKeyOwner, } from "./owner.js";
10
10
  export * from "./proof.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "1.0.0-dev.397+5d90f7d1",
3
+ "version": "1.0.0-dev.399+936004bd",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -4,7 +4,7 @@ import type { Actor } from "../vocab/actor.js";
4
4
  import type { Activity, CryptographicKey } from "../vocab/mod.js";
5
5
  import type { Object } from "../vocab/vocab.js";
6
6
  import type { PageItems } from "./collection.js";
7
- import type { Context, RequestContext } from "./context.js";
7
+ import type { Context, InboxContext, RequestContext } from "./context.js";
8
8
  import type { SenderKeyPair } from "./send.js";
9
9
  /**
10
10
  * A callback that dispatches a {@link NodeInfo} object.
@@ -86,12 +86,15 @@ export type CollectionCursor<TContext extends Context<TContextData>, TContextDat
86
86
  *
87
87
  * @typeParam TContextData The context data to pass to the {@link Context}.
88
88
  * @typeParam TActivity The type of activity to listen for.
89
+ * @param context The inbox context.
90
+ * @param activity The activity that was received.
89
91
  */
90
- export type InboxListener<TContextData, TActivity extends Activity> = (context: Context<TContextData>, activity: TActivity) => void | Promise<void>;
92
+ export type InboxListener<TContextData, TActivity extends Activity> = (context: InboxContext<TContextData>, activity: TActivity) => void | Promise<void>;
91
93
  /**
92
94
  * A callback that handles errors in an inbox.
93
95
  *
94
96
  * @typeParam TContextData The context data to pass to the {@link Context}.
97
+ * @param context The inbox context.
95
98
  */
96
99
  export type InboxErrorHandler<TContextData> = (context: Context<TContextData>, error: Error) => void | Promise<void>;
97
100
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/federation/callback.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"callback.d.ts","sourceRoot":"","sources":["../../src/federation/callback.ts"],"names":[],"mappings":"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"}