@fedify/fedify 2.1.0-dev.503 → 2.1.0-dev.523

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.
Files changed (78) hide show
  1. package/dist/{builder-BHUnSQtB.js → builder-CJkMYxxc.js} +9 -3
  2. package/dist/compat/mod.d.cts +3 -3
  3. package/dist/compat/mod.d.ts +3 -3
  4. package/dist/compat/transformers.test.js +12 -12
  5. package/dist/{context-DZJhUmzF.d.ts → context--RwChtri.d.ts} +54 -2
  6. package/dist/{context-D3QkEtZd.d.cts → context-DL0cPpPV.d.cts} +54 -2
  7. package/dist/{deno-BYerLnry.js → deno-CQdJQjC5.js} +1 -1
  8. package/dist/{docloader-MSkogD2T.js → docloader-Cyl0-S8m.js} +2 -2
  9. package/dist/federation/builder.test.js +14 -3
  10. package/dist/federation/handler.test.js +97 -13
  11. package/dist/federation/idempotency.test.js +12 -12
  12. package/dist/federation/inbox.test.js +2 -2
  13. package/dist/federation/keycache.test.js +46 -2
  14. package/dist/federation/middleware.test.js +206 -12
  15. package/dist/federation/mod.cjs +4 -4
  16. package/dist/federation/mod.d.cts +4 -4
  17. package/dist/federation/mod.d.ts +4 -4
  18. package/dist/federation/mod.js +4 -4
  19. package/dist/federation/send.test.js +5 -5
  20. package/dist/federation/webfinger.test.js +12 -12
  21. package/dist/{http-DkHdFfrc.d.ts → http-BbfOqHGG.d.ts} +80 -8
  22. package/dist/{http-DJT6NciB.cjs → http-D6a6mMc0.cjs} +305 -99
  23. package/dist/{http-CSX1-Mgi.js → http-DJmytoC2.js} +295 -101
  24. package/dist/{http-S2U3qDwN.js → http-DK0CTomU.js} +153 -57
  25. package/dist/{http-Cz3MlXAZ.d.cts → http-DsqqmkXi.d.cts} +80 -8
  26. package/dist/{inbox-BaA0g5I_.js → inbox-CWa6sqsk.js} +1 -1
  27. package/dist/{key-DCdTVZiK.js → key-DRgvVevp.js} +145 -47
  28. package/dist/keycache-C7k8s1Bk.js +102 -0
  29. package/dist/{kv-cache-Vtxhbo1W.cjs → kv-cache-DPtsJ1sL.cjs} +1 -1
  30. package/dist/{kv-cache-CQPL_aGY.js → kv-cache-MPcS_mGG.js} +1 -1
  31. package/dist/{ld-CrX7pQda.js → ld-s9_8WfBc.js} +2 -2
  32. package/dist/{middleware-CfI9C9Xy.js → middleware-2XtoTBq0.js} +12 -12
  33. package/dist/{middleware-MlO5iUeZ.js → middleware-Ajnk9qHB.js} +158 -22
  34. package/dist/middleware-BgCIhb_C.cjs +12 -0
  35. package/dist/{middleware-D4S6i4A_.cjs → middleware-BoCzk7-G.cjs} +158 -22
  36. package/dist/{middleware-C8PKuPrm.js → middleware-DGUNDGCl.js} +4 -4
  37. package/dist/{middleware-BelSJK7m.js → middleware-Dn9UDJZP.js} +100 -24
  38. package/dist/{mod-CwZXZJ9d.d.ts → mod-BugwI0JN.d.ts} +1 -1
  39. package/dist/{mod-DPkRU3EK.d.cts → mod-CFBU2OT3.d.cts} +1 -1
  40. package/dist/{mod-DUWcVv49.d.ts → mod-CvxylbuV.d.ts} +1 -1
  41. package/dist/{mod-DVwHUI_x.d.cts → mod-DE8MYisy.d.cts} +1 -1
  42. package/dist/{mod-DXsQakeS.d.cts → mod-DKG0ovjR.d.cts} +1 -1
  43. package/dist/{mod-DnSsduJF.d.ts → mod-DcfFNgYf.d.ts} +1 -1
  44. package/dist/{mod-Di3W5OdP.d.cts → mod-Dp0kK0hO.d.cts} +1 -1
  45. package/dist/{mod-DosD6NsG.d.ts → mod-Z7lIaCfo.d.ts} +1 -1
  46. package/dist/mod.cjs +8 -4
  47. package/dist/mod.d.cts +8 -8
  48. package/dist/mod.d.ts +8 -8
  49. package/dist/mod.js +7 -5
  50. package/dist/nodeinfo/handler.test.js +12 -12
  51. package/dist/otel/exporter.test.js +43 -2
  52. package/dist/otel/mod.cjs +7 -1
  53. package/dist/otel/mod.d.cts +12 -0
  54. package/dist/otel/mod.d.ts +12 -0
  55. package/dist/otel/mod.js +7 -1
  56. package/dist/{owner-BAlnLKMO.js → owner-Cx8gV-j4.js} +1 -1
  57. package/dist/{proof-DMgHaXNJ.js → proof-CDr3NP3R.js} +2 -2
  58. package/dist/{proof-BgUVmaJz.js → proof-Le4DAkqb.js} +1 -1
  59. package/dist/{proof-CR5RUAmy.cjs → proof-qHcNgE5i.cjs} +1 -1
  60. package/dist/{send-B2aZYf9A.js → send-DreBSY1U.js} +2 -2
  61. package/dist/sig/http.test.js +85 -5
  62. package/dist/sig/key.test.js +70 -3
  63. package/dist/sig/ld.test.js +3 -3
  64. package/dist/sig/mod.cjs +4 -2
  65. package/dist/sig/mod.d.cts +3 -3
  66. package/dist/sig/mod.d.ts +3 -3
  67. package/dist/sig/mod.js +3 -3
  68. package/dist/sig/owner.test.js +3 -3
  69. package/dist/sig/proof.test.js +3 -3
  70. package/dist/testing/mod.d.ts +92 -0
  71. package/dist/utils/docloader.test.js +4 -4
  72. package/dist/utils/mod.cjs +2 -2
  73. package/dist/utils/mod.d.cts +2 -2
  74. package/dist/utils/mod.d.ts +2 -2
  75. package/dist/utils/mod.js +2 -2
  76. package/package.json +5 -5
  77. package/dist/keycache-DRxpZ5r9.js +0 -48
  78. package/dist/middleware-D4XcpSBG.cjs +0 -12
@@ -3,9 +3,9 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { deno_default } from "./deno-BYerLnry.js";
6
+ import { deno_default } from "./deno-CQdJQjC5.js";
7
7
  import { Router, RouterError } from "./router-D9eI0s4b.js";
8
- import { InboxListenerSet } from "./inbox-BaA0g5I_.js";
8
+ import { InboxListenerSet } from "./inbox-CWa6sqsk.js";
9
9
  import { getLogger } from "@logtape/logtape";
10
10
  import { getTypeId } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -29,6 +29,7 @@ var FederationBuilderImpl = class {
29
29
  inboxListeners;
30
30
  inboxErrorHandler;
31
31
  sharedInboxKeyDispatcher;
32
+ unverifiedActivityHandler;
32
33
  outboxPermanentFailureHandler;
33
34
  idempotencyStrategy;
34
35
  collectionTypeIds;
@@ -45,7 +46,7 @@ var FederationBuilderImpl = class {
45
46
  this.collectionTypeIds = {};
46
47
  }
47
48
  async build(options) {
48
- const { FederationImpl } = await import("./middleware-CfI9C9Xy.js");
49
+ const { FederationImpl } = await import("./middleware-2XtoTBq0.js");
49
50
  const f = new FederationImpl(options);
50
51
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
51
52
  f.router = this.router.clone();
@@ -67,6 +68,7 @@ var FederationBuilderImpl = class {
67
68
  f.inboxListeners = this.inboxListeners?.clone();
68
69
  f.inboxErrorHandler = this.inboxErrorHandler;
69
70
  f.sharedInboxKeyDispatcher = this.sharedInboxKeyDispatcher;
71
+ f.unverifiedActivityHandler = this.unverifiedActivityHandler;
70
72
  f.outboxPermanentFailureHandler = this.outboxPermanentFailureHandler;
71
73
  f.idempotencyStrategy = this.idempotencyStrategy;
72
74
  return f;
@@ -443,6 +445,10 @@ var FederationBuilderImpl = class {
443
445
  this.inboxErrorHandler = handler;
444
446
  return setters;
445
447
  },
448
+ onUnverifiedActivity: (handler) => {
449
+ this.unverifiedActivityHandler = handler;
450
+ return setters;
451
+ },
446
452
  setSharedKeyDispatcher: (dispatcher) => {
447
453
  this.sharedInboxKeyDispatcher = dispatcher;
448
454
  return setters;
@@ -1,7 +1,7 @@
1
1
  import "../client-C97KOq3x.cjs";
2
- import "../http-Cz3MlXAZ.cjs";
2
+ import "../http-DsqqmkXi.cjs";
3
3
  import "../owner-1AbPBOOZ.cjs";
4
- import { ActivityTransformer } from "../context-D3QkEtZd.cjs";
4
+ import { ActivityTransformer } from "../context-DL0cPpPV.cjs";
5
5
  import "../kv-BL4nlICN.cjs";
6
- import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "../mod-DVwHUI_x.cjs";
6
+ import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "../mod-DE8MYisy.cjs";
7
7
  export { ActivityTransformer, actorDehydrator, autoIdAssigner, getDefaultActivityTransformers };
@@ -1,9 +1,9 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import "../client-BxMZiQaD.js";
4
- import "../http-DkHdFfrc.js";
4
+ import "../http-BbfOqHGG.js";
5
5
  import "../owner-gd0Q9FuU.js";
6
- import { ActivityTransformer } from "../context-DZJhUmzF.js";
6
+ import { ActivityTransformer } from "../context--RwChtri.js";
7
7
  import "../kv-DXEUEP6z.js";
8
- import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "../mod-DosD6NsG.js";
8
+ import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "../mod-Z7lIaCfo.js";
9
9
  export { ActivityTransformer, actorDehydrator, autoIdAssigner, getDefaultActivityTransformers };
@@ -8,25 +8,25 @@ import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import { assert } from "../assert-MZs1qjMx.js";
9
9
  import { assertInstanceOf } from "../assert_instance_of-DHz7EHNU.js";
10
10
  import { MemoryKvStore } from "../kv-QzKcOQgP.js";
11
- import "../deno-BYerLnry.js";
12
- import { FederationImpl, actorDehydrator, autoIdAssigner } from "../middleware-BelSJK7m.js";
11
+ import "../deno-CQdJQjC5.js";
12
+ import { FederationImpl, actorDehydrator, autoIdAssigner } from "../middleware-Dn9UDJZP.js";
13
13
  import "../client-Dg7OfUDA.js";
14
14
  import "../router-D9eI0s4b.js";
15
15
  import "../types-CPz01LGH.js";
16
- import "../key-DCdTVZiK.js";
17
- import "../http-S2U3qDwN.js";
18
- import "../ld-CrX7pQda.js";
19
- import "../owner-BAlnLKMO.js";
20
- import "../proof-DMgHaXNJ.js";
21
- import "../docloader-MSkogD2T.js";
16
+ import "../key-DRgvVevp.js";
17
+ import "../http-DK0CTomU.js";
18
+ import "../ld-s9_8WfBc.js";
19
+ import "../owner-Cx8gV-j4.js";
20
+ import "../proof-CDr3NP3R.js";
21
+ import "../docloader-Cyl0-S8m.js";
22
22
  import "../kv-cache-B__dHl7g.js";
23
- import "../inbox-BaA0g5I_.js";
24
- import "../builder-BHUnSQtB.js";
23
+ import "../inbox-CWa6sqsk.js";
24
+ import "../builder-CJkMYxxc.js";
25
25
  import "../collection-CcnIw1qY.js";
26
- import "../keycache-DRxpZ5r9.js";
26
+ import "../keycache-C7k8s1Bk.js";
27
27
  import "../negotiation-5NPJL6zp.js";
28
28
  import "../retry-D4GJ670a.js";
29
- import "../send-B2aZYf9A.js";
29
+ import "../send-DreBSY1U.js";
30
30
  import { Follow, Person } from "@fedify/vocab";
31
31
 
32
32
  //#region src/compat/transformers.test.ts
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { GetNodeInfoOptions, JsonValue, NodeInfo } from "./client-BxMZiQaD.js";
4
- import { HttpMessageSignaturesSpec } from "./http-DkHdFfrc.js";
4
+ import { HttpMessageSignaturesSpec, VerifyRequestFailureReason } from "./http-BbfOqHGG.js";
5
5
  import { GetKeyOwnerOptions } from "./owner-gd0Q9FuU.js";
6
6
  import { KvKey, KvStore } from "./kv-DXEUEP6z.js";
7
7
  import { Activity, Actor, Collection, CryptographicKey, Hashtag, Link, LookupObjectOptions, Multikey, Object as Object$1, Recipient, TraverseCollectionOptions } from "@fedify/vocab";
@@ -216,6 +216,29 @@ type CollectionCursor<TContext extends Context<TContextData>, TContextData, TFil
216
216
  */
217
217
  type InboxListener<TContextData, TActivity extends Activity> = (context: InboxContext<TContextData>, activity: TActivity) => void | Promise<void>;
218
218
  /**
219
+ * The reason why an incoming activity could not be verified.
220
+ *
221
+ * Unlike inbox listeners registered through {@link InboxListenerSetters.on},
222
+ * unverified activity handlers are called only when the activity payload could
223
+ * be parsed but its HTTP signatures could not be verified.
224
+ *
225
+ * @since 2.1.0
226
+ */
227
+ type UnverifiedActivityReason = VerifyRequestFailureReason;
228
+ /**
229
+ * A callback that handles activities whose signatures could not be verified.
230
+ *
231
+ * Returning a {@link Response} overrides Fedify's default `401 Unauthorized`
232
+ * response. Returning `void` keeps the default behavior.
233
+ *
234
+ * @template TContextData The context data to pass to the {@link Context}.
235
+ * @param context The request context.
236
+ * @param activity The incoming activity that could be parsed.
237
+ * @param reason The reason why signature verification failed.
238
+ * @since 2.1.0
239
+ */
240
+ type UnverifiedActivityHandler<TContextData> = (context: RequestContext<TContextData>, activity: Activity, reason: UnverifiedActivityReason) => void | Response | Promise<void | Response>;
241
+ /**
219
242
  * A callback that handles errors in an inbox.
220
243
  *
221
244
  * @template TContextData The context data to pass to the {@link Context}.
@@ -1628,6 +1651,35 @@ interface InboxListenerSetters<TContextData> {
1628
1651
  */
1629
1652
  onError(handler: InboxErrorHandler<TContextData>): InboxListenerSetters<TContextData>;
1630
1653
  /**
1654
+ * Registers a callback for incoming activities whose HTTP signatures could
1655
+ * not be verified.
1656
+ *
1657
+ * The regular inbox listeners registered through {@link on} continue to
1658
+ * receive only verified activities. This hook is an opt-in escape hatch for
1659
+ * applications that need to inspect unverified deliveries and optionally
1660
+ * override the default `401 Unauthorized` response.
1661
+ *
1662
+ * @example
1663
+ * ``` typescript
1664
+ * federation
1665
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
1666
+ * .onUnverifiedActivity((ctx, activity, reason) => {
1667
+ * if (
1668
+ * reason.type === "keyFetchError" &&
1669
+ * "status" in reason.result &&
1670
+ * reason.result.status === 410
1671
+ * ) {
1672
+ * return new Response(null, { status: 202 });
1673
+ * }
1674
+ * });
1675
+ * ```
1676
+ *
1677
+ * @param handler A callback to handle an unverified activity.
1678
+ * @returns The setters object so that settings can be chained.
1679
+ * @since 2.1.0
1680
+ */
1681
+ onUnverifiedActivity(handler: UnverifiedActivityHandler<TContextData>): InboxListenerSetters<TContextData>;
1682
+ /**
1631
1683
  * Configures a callback to dispatch the key pair for the authenticated
1632
1684
  * document loader of the {@link Context} passed to the shared inbox listener.
1633
1685
  *
@@ -2493,4 +2545,4 @@ interface ActorKeyPair extends CryptoKeyPair {
2493
2545
  readonly multikey: Multikey;
2494
2546
  }
2495
2547
  //#endregion
2496
- export { ActivityTransformer, ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, OutboxPermanentFailureHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityError, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, respondWithObject, respondWithObjectIfAcceptable };
2548
+ export { ActivityTransformer, ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, OutboxPermanentFailureHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityError, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, UnverifiedActivityHandler, UnverifiedActivityReason, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, respondWithObject, respondWithObjectIfAcceptable };
@@ -1,5 +1,5 @@
1
1
  import { GetNodeInfoOptions, JsonValue, NodeInfo } from "./client-C97KOq3x.cjs";
2
- import { HttpMessageSignaturesSpec } from "./http-Cz3MlXAZ.cjs";
2
+ import { HttpMessageSignaturesSpec, VerifyRequestFailureReason } from "./http-DsqqmkXi.cjs";
3
3
  import { GetKeyOwnerOptions } from "./owner-1AbPBOOZ.cjs";
4
4
  import { KvKey, KvStore } from "./kv-BL4nlICN.cjs";
5
5
  import { Activity, Actor, Collection, CryptographicKey, Hashtag, Link, LookupObjectOptions, Multikey, Object as Object$1, Recipient, TraverseCollectionOptions } from "@fedify/vocab";
@@ -214,6 +214,29 @@ type CollectionCursor<TContext extends Context<TContextData>, TContextData, TFil
214
214
  */
215
215
  type InboxListener<TContextData, TActivity extends Activity> = (context: InboxContext<TContextData>, activity: TActivity) => void | Promise<void>;
216
216
  /**
217
+ * The reason why an incoming activity could not be verified.
218
+ *
219
+ * Unlike inbox listeners registered through {@link InboxListenerSetters.on},
220
+ * unverified activity handlers are called only when the activity payload could
221
+ * be parsed but its HTTP signatures could not be verified.
222
+ *
223
+ * @since 2.1.0
224
+ */
225
+ type UnverifiedActivityReason = VerifyRequestFailureReason;
226
+ /**
227
+ * A callback that handles activities whose signatures could not be verified.
228
+ *
229
+ * Returning a {@link Response} overrides Fedify's default `401 Unauthorized`
230
+ * response. Returning `void` keeps the default behavior.
231
+ *
232
+ * @template TContextData The context data to pass to the {@link Context}.
233
+ * @param context The request context.
234
+ * @param activity The incoming activity that could be parsed.
235
+ * @param reason The reason why signature verification failed.
236
+ * @since 2.1.0
237
+ */
238
+ type UnverifiedActivityHandler<TContextData> = (context: RequestContext<TContextData>, activity: Activity, reason: UnverifiedActivityReason) => void | Response | Promise<void | Response>;
239
+ /**
217
240
  * A callback that handles errors in an inbox.
218
241
  *
219
242
  * @template TContextData The context data to pass to the {@link Context}.
@@ -1626,6 +1649,35 @@ interface InboxListenerSetters<TContextData> {
1626
1649
  */
1627
1650
  onError(handler: InboxErrorHandler<TContextData>): InboxListenerSetters<TContextData>;
1628
1651
  /**
1652
+ * Registers a callback for incoming activities whose HTTP signatures could
1653
+ * not be verified.
1654
+ *
1655
+ * The regular inbox listeners registered through {@link on} continue to
1656
+ * receive only verified activities. This hook is an opt-in escape hatch for
1657
+ * applications that need to inspect unverified deliveries and optionally
1658
+ * override the default `401 Unauthorized` response.
1659
+ *
1660
+ * @example
1661
+ * ``` typescript
1662
+ * federation
1663
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
1664
+ * .onUnverifiedActivity((ctx, activity, reason) => {
1665
+ * if (
1666
+ * reason.type === "keyFetchError" &&
1667
+ * "status" in reason.result &&
1668
+ * reason.result.status === 410
1669
+ * ) {
1670
+ * return new Response(null, { status: 202 });
1671
+ * }
1672
+ * });
1673
+ * ```
1674
+ *
1675
+ * @param handler A callback to handle an unverified activity.
1676
+ * @returns The setters object so that settings can be chained.
1677
+ * @since 2.1.0
1678
+ */
1679
+ onUnverifiedActivity(handler: UnverifiedActivityHandler<TContextData>): InboxListenerSetters<TContextData>;
1680
+ /**
1629
1681
  * Configures a callback to dispatch the key pair for the authenticated
1630
1682
  * document loader of the {@link Context} passed to the shared inbox listener.
1631
1683
  *
@@ -2491,4 +2543,4 @@ interface ActorKeyPair extends CryptoKeyPair {
2491
2543
  readonly multikey: Multikey;
2492
2544
  }
2493
2545
  //#endregion
2494
- export { ActivityTransformer, ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, OutboxPermanentFailureHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityError, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, respondWithObject, respondWithObjectIfAcceptable };
2546
+ export { ActivityTransformer, ActorAliasMapper, ActorCallbackSetters, ActorDispatcher, ActorHandleMapper, ActorKeyPair, ActorKeyPairsDispatcher, AuthorizePredicate, CollectionCallbackSetters, CollectionCounter, CollectionCursor, CollectionDispatcher, ConstructorWithTypeId, Context, CreateExponentialBackoffPolicyOptions, CustomCollectionCallbackSetters, CustomCollectionCounter, CustomCollectionCursor, CustomCollectionDispatcher, Federatable, Federation, FederationBuilder, FederationFetchOptions, FederationKvPrefixes, FederationOptions, FederationOrigin, FederationQueueOptions, FederationStartQueueOptions, ForwardActivityOptions, GetSignedKeyOptions, IdempotencyKeyCallback, IdempotencyStrategy, InProcessMessageQueue, InProcessMessageQueueOptions, InboxContext, InboxErrorHandler, InboxListener, InboxListenerSetters, Message, MessageQueue, MessageQueueEnqueueOptions, MessageQueueListenOptions, NodeInfoDispatcher, ObjectAuthorizePredicate, ObjectCallbackSetters, ObjectDispatcher, OutboxErrorHandler, OutboxPermanentFailureHandler, PageItems, ParallelMessageQueue, ParseUriResult, RequestContext, RespondWithObjectOptions, RetryContext, RetryPolicy, Rfc6570Expression, RouteActivityOptions, Router, RouterError, RouterOptions, RouterRouteResult, SendActivityError, SendActivityOptions, SendActivityOptionsForCollection, SenderKeyPair, SharedInboxKeyDispatcher, UnverifiedActivityHandler, UnverifiedActivityReason, WebFingerLinksDispatcher, buildCollectionSynchronizationHeader, createExponentialBackoffPolicy, createFederation, createFederationBuilder, digest, respondWithObject, respondWithObjectIfAcceptable };
@@ -5,7 +5,7 @@
5
5
 
6
6
  //#region deno.json
7
7
  var name = "@fedify/fedify";
8
- var version = "2.1.0-dev.503+1172d43b";
8
+ var version = "2.1.0-dev.523+150998a5";
9
9
  var license = "MIT";
10
10
  var exports = {
11
11
  ".": "./src/mod.ts",
@@ -3,8 +3,8 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { validateCryptoKey } from "./key-DCdTVZiK.js";
7
- import { doubleKnock } from "./http-S2U3qDwN.js";
6
+ import { validateCryptoKey } from "./key-DRgvVevp.js";
7
+ import { doubleKnock } from "./http-DK0CTomU.js";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { curry } from "es-toolkit";
10
10
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
@@ -8,10 +8,10 @@ import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
10
  import { MemoryKvStore } from "../kv-QzKcOQgP.js";
11
- import "../deno-BYerLnry.js";
11
+ import "../deno-CQdJQjC5.js";
12
12
  import "../router-D9eI0s4b.js";
13
- import "../inbox-BaA0g5I_.js";
14
- import { createFederationBuilder } from "../builder-BHUnSQtB.js";
13
+ import "../inbox-CWa6sqsk.js";
14
+ import { createFederationBuilder } from "../builder-CJkMYxxc.js";
15
15
  import { assertExists } from "../std__assert-DWivtrGR.js";
16
16
  import "../assert_rejects-Ce45JcFg.js";
17
17
  import { assertThrows } from "../assert_throws-BNXdRGWP.js";
@@ -123,6 +123,17 @@ test("FederationBuilder", async (t) => {
123
123
  const implRfc = federationRfc;
124
124
  assertEquals(implRfc.firstKnock, "rfc9421");
125
125
  });
126
+ await t.step("should copy unverified activity handler into built federation", async () => {
127
+ const builder = createFederationBuilder();
128
+ const kv = new MemoryKvStore();
129
+ const handler = (_ctx, _activity, _reason) => {
130
+ return;
131
+ };
132
+ builder.setInboxListeners("/users/{identifier}/inbox").onUnverifiedActivity(handler);
133
+ const federation = await builder.build({ kv });
134
+ const impl = federation;
135
+ assertEquals(impl.unverifiedActivityHandler, handler);
136
+ });
126
137
  await t.step("should register multiple object dispatchers and verify them", async () => {
127
138
  const builder = createFederationBuilder();
128
139
  const kv = new MemoryKvStore();
@@ -8,25 +8,25 @@ import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import { assert } from "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
10
  import { MemoryKvStore } from "../kv-QzKcOQgP.js";
11
- import "../deno-BYerLnry.js";
12
- import { createFederation, handleActor, handleCollection, handleCustomCollection, handleInbox, handleObject, respondWithObject, respondWithObjectIfAcceptable } from "../middleware-BelSJK7m.js";
11
+ import "../deno-CQdJQjC5.js";
12
+ import { createFederation, handleActor, handleCollection, handleCustomCollection, handleInbox, handleObject, respondWithObject, respondWithObjectIfAcceptable } from "../middleware-Dn9UDJZP.js";
13
13
  import "../client-Dg7OfUDA.js";
14
14
  import "../router-D9eI0s4b.js";
15
15
  import "../types-CPz01LGH.js";
16
- import "../key-DCdTVZiK.js";
17
- import { signRequest } from "../http-S2U3qDwN.js";
18
- import "../ld-CrX7pQda.js";
19
- import "../owner-BAlnLKMO.js";
20
- import "../proof-DMgHaXNJ.js";
21
- import "../docloader-MSkogD2T.js";
16
+ import "../key-DRgvVevp.js";
17
+ import { signRequest } from "../http-DK0CTomU.js";
18
+ import "../ld-s9_8WfBc.js";
19
+ import "../owner-Cx8gV-j4.js";
20
+ import "../proof-CDr3NP3R.js";
21
+ import "../docloader-Cyl0-S8m.js";
22
22
  import "../kv-cache-B__dHl7g.js";
23
- import { InboxListenerSet } from "../inbox-BaA0g5I_.js";
24
- import "../builder-BHUnSQtB.js";
23
+ import { InboxListenerSet } from "../inbox-CWa6sqsk.js";
24
+ import "../builder-CJkMYxxc.js";
25
25
  import "../collection-CcnIw1qY.js";
26
- import "../keycache-DRxpZ5r9.js";
26
+ import "../keycache-C7k8s1Bk.js";
27
27
  import "../negotiation-5NPJL6zp.js";
28
28
  import "../retry-D4GJ670a.js";
29
- import "../send-B2aZYf9A.js";
29
+ import "../send-DreBSY1U.js";
30
30
  import "../std__assert-DWivtrGR.js";
31
31
  import "../assert_rejects-Ce45JcFg.js";
32
32
  import "../assert_throws-BNXdRGWP.js";
@@ -34,6 +34,7 @@ import "../assert_not_equals-C80BG-_5.js";
34
34
  import { createInboxContext, createRequestContext } from "../context-CZ5llAss.js";
35
35
  import { rsaPrivateKey3, rsaPublicKey2, rsaPublicKey3 } from "../keys-ZbcByPg9.js";
36
36
  import { Create, Note, Person } from "@fedify/vocab";
37
+ import { FetchError } from "@fedify/vocab-runtime";
37
38
 
38
39
  //#region src/federation/handler.test.ts
39
40
  test("handleActor()", async () => {
@@ -1562,12 +1563,95 @@ test("handleInbox() records OpenTelemetry span events", async () => {
1562
1563
  assertEquals(events.length, 1);
1563
1564
  const event = events[0];
1564
1565
  assert(event.attributes != null);
1565
- assertEquals(event.attributes["activitypub.activity.verified"], true);
1566
+ assertEquals(event.attributes["activitypub.activity.verified"], false);
1566
1567
  assertEquals(event.attributes["http_signatures.verified"], false);
1567
1568
  assert(typeof event.attributes["activitypub.activity.json"] === "string");
1568
1569
  const recordedActivity = JSON.parse(event.attributes["activitypub.activity.json"]);
1569
1570
  assertEquals(recordedActivity.id, "https://example.com/activity");
1570
1571
  assertEquals(recordedActivity.type, "Create");
1571
1572
  });
1573
+ test("handleInbox() records unverified HTTP signature details", async () => {
1574
+ const [tracerProvider, exporter] = createTestTracerProvider();
1575
+ const kv = new MemoryKvStore();
1576
+ const federation = createFederation({
1577
+ kv,
1578
+ tracerProvider
1579
+ });
1580
+ const keyId = new URL("https://gone.example/users/someone#main-key");
1581
+ const activity = new Create({
1582
+ id: new URL("https://example.com/activity"),
1583
+ actor: new URL("https://gone.example/users/someone"),
1584
+ object: new Note({
1585
+ id: new URL("https://example.com/note"),
1586
+ content: "Hello, world!"
1587
+ })
1588
+ });
1589
+ const request = new Request("https://example.com/users/someone/inbox", {
1590
+ method: "POST",
1591
+ headers: { "Content-Type": "application/activity+json" },
1592
+ body: JSON.stringify(await activity.toJsonLd())
1593
+ });
1594
+ const signed = await signRequest(request, rsaPrivateKey3, keyId);
1595
+ const documentLoader = (url) => {
1596
+ if (url === keyId.href) throw new FetchError(keyId, `HTTP 410: ${keyId.href}`, new Response(null, { status: 410 }));
1597
+ return mockDocumentLoader(url);
1598
+ };
1599
+ const context = createRequestContext({
1600
+ federation,
1601
+ request: signed,
1602
+ url: new URL(signed.url),
1603
+ data: void 0,
1604
+ documentLoader,
1605
+ contextLoader: mockDocumentLoader,
1606
+ getActorUri(identifier) {
1607
+ return new URL(`https://example.com/users/${identifier}`);
1608
+ }
1609
+ });
1610
+ const actorDispatcher = (ctx, identifier) => {
1611
+ if (identifier !== "someone") return null;
1612
+ return new Person({
1613
+ id: ctx.getActorUri(identifier),
1614
+ name: "Someone",
1615
+ inbox: new URL("https://example.com/users/someone/inbox"),
1616
+ publicKey: rsaPublicKey2
1617
+ });
1618
+ };
1619
+ const response = await handleInbox(signed, {
1620
+ recipient: "someone",
1621
+ context,
1622
+ inboxContextFactory(_activity) {
1623
+ return createInboxContext({
1624
+ ...context,
1625
+ clone: void 0
1626
+ });
1627
+ },
1628
+ kv,
1629
+ kvPrefixes: {
1630
+ activityIdempotence: ["activityIdempotence"],
1631
+ publicKey: ["publicKey"]
1632
+ },
1633
+ actorDispatcher,
1634
+ inboxListeners: new InboxListenerSet(),
1635
+ inboxErrorHandler: void 0,
1636
+ unverifiedActivityHandler() {
1637
+ return new Response("", { status: 202 });
1638
+ },
1639
+ onNotFound: (_request) => new Response("Not found", { status: 404 }),
1640
+ signatureTimeWindow: false,
1641
+ skipSignatureVerification: false,
1642
+ tracerProvider
1643
+ });
1644
+ assertEquals(response.status, 202);
1645
+ const verifySpans = exporter.getSpans("http_signatures.verify");
1646
+ assertEquals(verifySpans.length, 1);
1647
+ assertEquals(verifySpans[0].attributes["http_signatures.failure_reason"], "keyFetchError");
1648
+ assertEquals(verifySpans[0].attributes["http_signatures.key_fetch_status"], 410);
1649
+ const events = exporter.getEvents("activitypub.inbox", "activitypub.activity.received");
1650
+ assertEquals(events.length, 1);
1651
+ const event = events[0];
1652
+ assert(event.attributes != null);
1653
+ assertEquals(event.attributes["http_signatures.failure_reason"], "keyFetchError");
1654
+ assertEquals(event.attributes["http_signatures.key_fetch_status"], 410);
1655
+ });
1572
1656
 
1573
1657
  //#endregion
@@ -8,25 +8,25 @@ import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
10
  import { MemoryKvStore } from "../kv-QzKcOQgP.js";
11
- import "../deno-BYerLnry.js";
12
- import { createFederation } from "../middleware-BelSJK7m.js";
11
+ import "../deno-CQdJQjC5.js";
12
+ import { createFederation } from "../middleware-Dn9UDJZP.js";
13
13
  import "../client-Dg7OfUDA.js";
14
14
  import "../router-D9eI0s4b.js";
15
15
  import "../types-CPz01LGH.js";
16
- import "../key-DCdTVZiK.js";
17
- import "../http-S2U3qDwN.js";
18
- import "../ld-CrX7pQda.js";
19
- import "../owner-BAlnLKMO.js";
20
- import { signObject } from "../proof-DMgHaXNJ.js";
21
- import "../docloader-MSkogD2T.js";
16
+ import "../key-DRgvVevp.js";
17
+ import "../http-DK0CTomU.js";
18
+ import "../ld-s9_8WfBc.js";
19
+ import "../owner-Cx8gV-j4.js";
20
+ import { signObject } from "../proof-CDr3NP3R.js";
21
+ import "../docloader-Cyl0-S8m.js";
22
22
  import "../kv-cache-B__dHl7g.js";
23
- import "../inbox-BaA0g5I_.js";
24
- import "../builder-BHUnSQtB.js";
23
+ import "../inbox-CWa6sqsk.js";
24
+ import "../builder-CJkMYxxc.js";
25
25
  import "../collection-CcnIw1qY.js";
26
- import "../keycache-DRxpZ5r9.js";
26
+ import "../keycache-C7k8s1Bk.js";
27
27
  import "../negotiation-5NPJL6zp.js";
28
28
  import "../retry-D4GJ670a.js";
29
- import "../send-B2aZYf9A.js";
29
+ import "../send-DreBSY1U.js";
30
30
  import "../std__assert-DWivtrGR.js";
31
31
  import "../assert_rejects-Ce45JcFg.js";
32
32
  import "../assert_throws-BNXdRGWP.js";
@@ -5,8 +5,8 @@
5
5
 
6
6
  import { test } from "../dist-B5f6a8Tt.js";
7
7
  import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
- import "../deno-BYerLnry.js";
9
- import { InboxListenerSet } from "../inbox-BaA0g5I_.js";
8
+ import "../deno-CQdJQjC5.js";
9
+ import { InboxListenerSet } from "../inbox-CWa6sqsk.js";
10
10
  import { assertThrows } from "../assert_throws-BNXdRGWP.js";
11
11
  import { Activity, Create, Invite, Offer, Update } from "@fedify/vocab";
12
12
 
@@ -8,7 +8,7 @@ import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import { assert } from "../assert-MZs1qjMx.js";
9
9
  import { assertInstanceOf } from "../assert_instance_of-DHz7EHNU.js";
10
10
  import { MemoryKvStore } from "../kv-QzKcOQgP.js";
11
- import { KvKeyCache } from "../keycache-DRxpZ5r9.js";
11
+ import { KvKeyCache } from "../keycache-C7k8s1Bk.js";
12
12
  import { CryptographicKey, Multikey } from "@fedify/vocab";
13
13
 
14
14
  //#region src/federation/keycache.test.ts
@@ -29,6 +29,7 @@ test("KvKeyCache.set()", async () => {
29
29
  });
30
30
  await cache.set(new URL("https://example.com/null"), null);
31
31
  assert(cache.nullKeys.has("https://example.com/null"));
32
+ assertEquals(await kv.get(["pk", "https://example.com/null"]), null);
32
33
  });
33
34
  test("KvKeyCache.get()", async () => {
34
35
  const kv = new MemoryKvStore();
@@ -49,8 +50,51 @@ test("KvKeyCache.get()", async () => {
49
50
  const multikey = await cache.get(new URL("https://example.com/key2"));
50
51
  assertInstanceOf(multikey, Multikey);
51
52
  assertEquals(multikey?.id?.href, "https://example.com/key2");
52
- cache.nullKeys.add("https://example.com/null");
53
+ cache.nullKeys.set("https://example.com/null", Temporal.Now.instant().add(Temporal.Duration.from({ minutes: 10 })));
53
54
  assertEquals(await cache.get(new URL("https://example.com/null")), null);
55
+ await kv.set(["pk", "https://example.com/null2"], null);
56
+ const cache2 = new KvKeyCache(kv, ["pk"]);
57
+ assertEquals(await cache2.get(new URL("https://example.com/null2")), null);
58
+ });
59
+ test("KvKeyCache fetch error metadata", async () => {
60
+ const kv = new MemoryKvStore();
61
+ const cache = new KvKeyCache(kv, ["pk"]);
62
+ const keyId = new URL("https://example.com/key");
63
+ await cache.setFetchError(keyId, {
64
+ status: 410,
65
+ response: new Response("gone", {
66
+ status: 410,
67
+ statusText: "Gone",
68
+ headers: { "content-type": "text/plain" }
69
+ })
70
+ });
71
+ const httpError = await cache.getFetchError(keyId);
72
+ assert(httpError != null && "status" in httpError);
73
+ if (httpError == null || !("status" in httpError)) throw new Error("Expected HTTP fetch error metadata.");
74
+ assertEquals(httpError.status, 410);
75
+ assertEquals(httpError.response.status, 410);
76
+ assertEquals(await httpError.response.text(), "gone");
77
+ await cache.setFetchError(keyId, { error: Object.assign(/* @__PURE__ */ new Error("boom"), { name: "TypeError" }) });
78
+ const nonHttpError = await cache.getFetchError(keyId);
79
+ assert(nonHttpError != null && "error" in nonHttpError);
80
+ if (nonHttpError == null || !("error" in nonHttpError)) throw new Error("Expected non-HTTP fetch error metadata.");
81
+ assertEquals(nonHttpError.error.name, "TypeError");
82
+ assertEquals(nonHttpError.error.message, "boom");
83
+ await cache.setFetchError(keyId, null);
84
+ assertEquals(await cache.getFetchError(keyId), void 0);
85
+ });
86
+ test("KvKeyCache unavailable entries expire", async () => {
87
+ const kv = new MemoryKvStore();
88
+ const cache = new KvKeyCache(kv, ["pk"], { unavailableKeyTtl: Temporal.Duration.from({ milliseconds: 1 }) });
89
+ const keyId = new URL("https://example.com/expired");
90
+ await cache.set(keyId, null);
91
+ await cache.setFetchError(keyId, {
92
+ status: 410,
93
+ response: new Response(null, { status: 410 })
94
+ });
95
+ await new Promise((resolve) => setTimeout(resolve, 10));
96
+ assertEquals(await cache.get(keyId), void 0);
97
+ assertEquals(await cache.getFetchError(keyId), void 0);
54
98
  });
55
99
 
56
100
  //#endregion