@fedify/fedify 2.5.0-dev.2446 → 2.5.0-dev.2448

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 (117) hide show
  1. package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
  2. package/dist/{builder-Da8b9O6v.mjs → builder-DbC8bn6k.mjs} +5 -5
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-heeXW2_A.mjs} +2 -2
  7. package/dist/{context-CQ9H8vNW.d.cts → context-BazwQrHn.d.cts} +304 -4
  8. package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
  9. package/dist/{context-BdB54V-A.d.ts → context-GlHrzdSP.d.ts} +304 -4
  10. package/dist/{deno-eNpfj4oR.mjs → deno-Dpewt_jJ.mjs} +1 -1
  11. package/dist/{docloader-B-0nqOB1.mjs → docloader-CpsDyqVN.mjs} +2 -2
  12. package/dist/federation/builder.test.mjs +1 -1
  13. package/dist/federation/compound-fanout.test.mjs +3 -3
  14. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  15. package/dist/federation/handler.test.mjs +21 -6
  16. package/dist/federation/hashlink-media.test.mjs +2 -2
  17. package/dist/federation/idempotency.test.mjs +2 -2
  18. package/dist/federation/inbox-actorless.test.d.mts +2 -0
  19. package/dist/federation/inbox-actorless.test.mjs +487 -0
  20. package/dist/federation/inbox-authentication.test.d.mts +2 -0
  21. package/dist/federation/inbox-authentication.test.mjs +116 -0
  22. package/dist/federation/inbox-report.test.mjs +5 -5
  23. package/dist/federation/inbox-view.test.d.mts +2 -0
  24. package/dist/federation/inbox-view.test.mjs +369 -0
  25. package/dist/federation/inbox.test.mjs +22 -12
  26. package/dist/federation/metrics.test.mjs +1 -1
  27. package/dist/federation/middleware.test.mjs +9 -9
  28. package/dist/federation/mod.cjs +2 -1
  29. package/dist/federation/mod.d.cts +3 -3
  30. package/dist/federation/mod.d.ts +3 -3
  31. package/dist/federation/mod.js +8 -2
  32. package/dist/federation/mq.test.mjs +36 -1
  33. package/dist/federation/outgoing-proof.test.mjs +4 -4
  34. package/dist/federation/portable-actor.test.mjs +3 -3
  35. package/dist/federation/portable-chain.test.mjs +3 -3
  36. package/dist/federation/portable-collection.test.mjs +4 -4
  37. package/dist/federation/portable-delivery.test.mjs +2 -2
  38. package/dist/federation/portable-gateway-keys.test.mjs +6 -6
  39. package/dist/federation/portable-inbox.test.mjs +4 -4
  40. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  41. package/dist/federation/portable-route-activity.test.mjs +2 -2
  42. package/dist/federation/portable.test.mjs +3 -3
  43. package/dist/federation/send.test.mjs +3 -3
  44. package/dist/federation/tasks/enqueue.test.mjs +3 -3
  45. package/dist/federation/tasks/tasks.test.mjs +4 -4
  46. package/dist/federation/temporal.test.mjs +1 -1
  47. package/dist/federation/webfinger.test.mjs +2 -2
  48. package/dist/{federation-BVH7drJA.cjs → federation-D8YexkP1.cjs} +1172 -131
  49. package/dist/{http-DlNxxW_5.js → http-BBr-VLil.js} +143 -16
  50. package/dist/{http-BUJ5SFh6.mjs → http-BZkyQIkb.mjs} +4 -4
  51. package/dist/{http-BfW3wyQ4.cjs → http-CAl7Num5.cjs} +161 -16
  52. package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
  53. package/dist/inbox-view-NE9wXdLo.mjs +385 -0
  54. package/dist/inbox-wDuMKaFW.mjs +312 -0
  55. package/dist/{kv-cache-D_m3A241.mjs → kv-cache-CTQSfkmp.mjs} +1 -1
  56. package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-CjgCOz3i.cjs} +1 -1
  57. package/dist/{kv-cache-7015lbrx.js → kv-cache-U7uz5nx4.js} +1 -1
  58. package/dist/{ld-CD2CsPxq.mjs → ld-CQFuiah9.mjs} +52 -9
  59. package/dist/ld-CcuZ4NZa.d.ts +250 -0
  60. package/dist/ld-Cel1RRZ5.d.cts +250 -0
  61. package/dist/{metrics-D3LHk6Z4.mjs → metrics-B-QxRSSR.mjs} +1 -1
  62. package/dist/{middleware-CCDXvXOG.mjs → middleware-BdlNa8BD.mjs} +1 -1
  63. package/dist/{middleware-CzoWvrdZ.mjs → middleware-CuVa0UuS.mjs} +459 -98
  64. package/dist/{middleware-N9eM16XE.js → middleware-P-AZEQMn.js} +1169 -141
  65. package/dist/mod-B2783_3v.d.cts +154 -0
  66. package/dist/mod-BHQE-7WQ.d.ts +154 -0
  67. package/dist/{mod-D0bxB8jS.d.ts → mod-DjZfJj9R.d.ts} +1 -1
  68. package/dist/{mod-B0dGgQsg.d.cts → mod-DvWOv2Ho.d.cts} +1 -1
  69. package/dist/mod.cjs +5 -4
  70. package/dist/mod.d.cts +6 -6
  71. package/dist/mod.d.ts +6 -6
  72. package/dist/mod.js +5 -5
  73. package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
  74. package/dist/nodeinfo/handler.test.mjs +2 -2
  75. package/dist/{owner-C5-FVX6t.mjs → owner-ccKkQOAf.mjs} +3 -3
  76. package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CndnpXfo.mjs} +2 -2
  77. package/dist/{portable-collection-CC2zNAje.js → portable-collection-DUlQ3s2M.js} +1 -1
  78. package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-TC-kvkVI.cjs} +1 -1
  79. package/dist/{portable-key-DNc_flH-.mjs → portable-key-D5tFOoV-.mjs} +1 -1
  80. package/dist/{proof-Cryb1c70.mjs → proof-DPoF06nz.mjs} +98 -10
  81. package/dist/{send-AF25pWaJ.mjs → send-CF44O1Df.mjs} +3 -3
  82. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  83. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  84. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  85. package/dist/sig/compound-proof.test.mjs +2 -2
  86. package/dist/sig/http.test.mjs +2 -2
  87. package/dist/sig/key.test.mjs +1 -1
  88. package/dist/sig/ld.test.mjs +1 -1
  89. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  90. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  91. package/dist/sig/mod.cjs +2 -2
  92. package/dist/sig/mod.d.cts +2 -2
  93. package/dist/sig/mod.d.ts +2 -2
  94. package/dist/sig/mod.js +2 -2
  95. package/dist/sig/owner.test.mjs +1 -1
  96. package/dist/sig/portable-collection.test.mjs +1 -1
  97. package/dist/sig/portable-interop.test.mjs +3 -3
  98. package/dist/sig/portable-key.test.mjs +5 -5
  99. package/dist/sig/proof.test.mjs +44 -4
  100. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  101. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  102. package/dist/sig/verification.test.mjs +3 -3
  103. package/dist/{temporal-BsAeuUJj.mjs → temporal-XlZ4Wb94.mjs} +1 -1
  104. package/dist/testing/kv.test.mjs +1 -1
  105. package/dist/testing/mod.d.mts +262 -2
  106. package/dist/testing/mod.mjs +2 -2
  107. package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
  108. package/dist/utils/docloader.test.mjs +2 -2
  109. package/dist/utils/kv-cache.test.mjs +1 -1
  110. package/dist/utils/mod.cjs +1 -1
  111. package/dist/utils/mod.js +1 -1
  112. package/package.json +7 -7
  113. package/dist/inbox-CHvMaIFX.mjs +0 -188
  114. package/dist/mod-BRpER-B0.d.ts +0 -320
  115. package/dist/mod-xAfaLS4T.d.cts +0 -320
  116. package/dist/owner-BF0L9cMt.d.cts +0 -78
  117. package/dist/owner-BSzr5-I8.d.ts +0 -78
@@ -5,7 +5,7 @@ import { Router } from "@fedify/uri-template";
5
5
  import { Activity, Actor, Collection, CryptographicKey, DataIntegrityProof, Hashtag, Link, LookupObjectOptions, Multikey, Note, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
6
6
  import { Link as Link$1, LookupWebFingerOptions, ResourceDescriptor } from "@fedify/webfinger";
7
7
  import { MeterProvider, TracerProvider } from "@opentelemetry/api";
8
- import { AuthenticatedDocumentLoaderFactory, DocumentLoader, DocumentLoaderFactory, GetUserAgentOptions, PortableObjectVerifier } from "@fedify/vocab-runtime";
8
+ import { AuthenticatedDocumentLoaderFactory, DocumentLoader, DocumentLoaderFactory, GetUserAgentOptions, PortableObjectVerifier, RemoteDocument } from "@fedify/vocab-runtime";
9
9
  import { MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
10
10
  import { StandardSchemaV1 } from "@standard-schema/spec";
11
11
 
@@ -416,6 +416,35 @@ interface GetKeyOwnerOptions {
416
416
  tracerProvider?: TracerProvider;
417
417
  }
418
418
  //#endregion
419
+ //#region src/federation/activity-listener.d.ts
420
+ /**
421
+ * Options for an inbox listener.
422
+ * @since 2.5.0
423
+ */
424
+ interface InboxListenerOptions {
425
+ /**
426
+ * Whether activities that declare no `actor` at all may reach the listener.
427
+ *
428
+ * Some protocols identify the requester without an `actor`; for example,
429
+ * Mastodon sends [FEP-7aa9] `FeatureRequest` activities without one. When
430
+ * this is `true`, such an activity is dispatched to the listener if Fedify
431
+ * authenticated at least one principal for it, i.e., the signer of its
432
+ * Object Integrity Proofs or Linked Data Signature, or otherwise the signer
433
+ * of the HTTP request, and its `id`, if any, has the same origin as that
434
+ * principal. The listener is then responsible for authorizing the request
435
+ * with {@link InboxContext.authentication}, e.g., with
436
+ * `featureInteraction.verifyRequest()` from
437
+ * `@fedify/interaction-controls`.
438
+ *
439
+ * An activity that declares an `actor` is never affected by this option;
440
+ * its actor must be authenticated as usual.
441
+ *
442
+ * [FEP-7aa9]: https://w3id.org/fep/7aa9
443
+ * @default `false`
444
+ */
445
+ readonly allowActorless?: boolean;
446
+ }
447
+ //#endregion
419
448
  //#region src/federation/inbox-report.d.ts
420
449
  /**
421
450
  * A JSON value retained from the incoming request.
@@ -458,6 +487,13 @@ type InboxAuthentication = {
458
487
  readonly type: "activityOriginMismatch";
459
488
  readonly activityId: URL;
460
489
  readonly actorIds: readonly URL[];
490
+ } | {
491
+ /**
492
+ * The activity declares no actor, and no principal was authenticated
493
+ * for it, although its listener allows activities without an actor.
494
+ * @since 2.5.0
495
+ */
496
+ readonly type: "noAuthenticatedPrincipal";
461
497
  };
462
498
  } | {
463
499
  readonly status: "skipped";
@@ -1311,6 +1347,18 @@ interface MessageQueue {
1311
1347
  * @since 2.4.0
1312
1348
  */
1313
1349
  readonly atomicEnqueueMany?: boolean;
1350
+ /**
1351
+ * The largest message, in bytes, that the backend can hold. Fedify compares
1352
+ * it with a conservative estimate of a message's serialized size: the
1353
+ * length of its JSON serialization encoded in UTF-8, plus 8 bytes for each
1354
+ * JSON value in it, plus what encoding strings beyond Latin-1 as UTF-16
1355
+ * adds, which also bounds binary serialization formats. When an
1356
+ * incoming activity would make a larger inbox message, Fedify processes the
1357
+ * activity right away instead of enqueueing it, as if there were no inbox
1358
+ * queue. When omitted, messages are enqueued regardless of their size.
1359
+ * @since 2.5.0
1360
+ */
1361
+ readonly maxMessageSize?: number;
1314
1362
  /**
1315
1363
  * Enqueues a message in the queue.
1316
1364
  * @param message The message to enqueue.
@@ -1349,6 +1397,176 @@ interface MessageQueue {
1349
1397
  getDepth?(): Promise<MessageQueueDepth>;
1350
1398
  }
1351
1399
  //#endregion
1400
+ //#region src/federation/inbox-authentication.d.ts
1401
+ /**
1402
+ * A party that Fedify authenticated for an incoming activity, independently
1403
+ * of the activity's declared `actor`.
1404
+ * @since 2.5.0
1405
+ */
1406
+ interface InboxPrincipal {
1407
+ /**
1408
+ * What {@link InboxPrincipal.id} is:
1409
+ *
1410
+ * - `"actor"`: an actor whose ownership of the verifying key Fedify
1411
+ * confirmed with the actor's own document.
1412
+ * - `"portableAuthority"`: a DID whose verification method made a valid
1413
+ * Object Integrity Proof. It authenticates the [FEP-ef61] portable
1414
+ * actors of that DID, and nothing else.
1415
+ *
1416
+ * [FEP-ef61]: https://w3id.org/fep/ef61
1417
+ */
1418
+ readonly kind: "actor" | "portableAuthority";
1419
+ /** The actor ID, or the DID if {@link kind} is `"portableAuthority"`. */
1420
+ readonly id: URL;
1421
+ /**
1422
+ * How the principal was authenticated:
1423
+ *
1424
+ * - `"httpSignatures"`: it signed the HTTP request.
1425
+ * - `"linkedDataSignatures"`: it made the Linked Data Signature of the
1426
+ * activity.
1427
+ * - `"objectIntegrityProofs"`: it made an Object Integrity Proof of the
1428
+ * activity.
1429
+ * - `"origin"`: the activity was fetched again from the actor's own web
1430
+ * origin, which happens only for `Context.routeActivity()`.
1431
+ */
1432
+ readonly mechanism: "httpSignatures" | "linkedDataSignatures" | "objectIntegrityProofs" | "origin";
1433
+ /** The key or verification method that authenticated the principal. */
1434
+ readonly keyId: URL | null;
1435
+ }
1436
+ /**
1437
+ * Who Fedify authenticated for an incoming activity, and the interpretation of
1438
+ * the activity it authenticated. Inbox listeners receive it as
1439
+ * {@link InboxContext.authentication}.
1440
+ *
1441
+ * Authenticated principals are independent of the activity's declared
1442
+ * `actor`: an activity forwarded by a relay is signed by the relay in its
1443
+ * HTTP request, but its document may be signed by its author, and some
1444
+ * activities, such as [FEP-7aa9] `FeatureRequest`s sent by Mastodon, declare
1445
+ * no `actor` at all. A valid signature alone does not authorize anything;
1446
+ * use {@link isActorAuthenticated} to check whether a specific actor was
1447
+ * authenticated.
1448
+ *
1449
+ * [FEP-7aa9]: https://w3id.org/fep/7aa9
1450
+ * @since 2.5.0
1451
+ */
1452
+ interface InboxAuthenticationEvidence {
1453
+ /**
1454
+ * Whether signatures were verified at all. It is `false` only when
1455
+ * signature verification is skipped, or when there is no evidence, e.g.,
1456
+ * for an activity queued by an older version of Fedify. In that case all
1457
+ * the principal lists are empty.
1458
+ */
1459
+ readonly verified: boolean;
1460
+ /**
1461
+ * The principals that signed the HTTP request, i.e., the server that
1462
+ * delivered the activity, which may have forwarded it. It is empty when
1463
+ * the activity document was authenticated by its own signatures, so that
1464
+ * HTTP Signatures did not need to be verified.
1465
+ */
1466
+ readonly requestSigners: readonly InboxPrincipal[];
1467
+ /**
1468
+ * The principals that made the verified Linked Data Signature or Object
1469
+ * Integrity Proofs of the activity document. All of them are listed when
1470
+ * there are several proofs.
1471
+ */
1472
+ readonly documentSigners: readonly InboxPrincipal[];
1473
+ /**
1474
+ * The actors authenticated because `Context.routeActivity()` fetched the
1475
+ * activity again from their own web origin. It is always empty for
1476
+ * activities delivered to an inbox.
1477
+ */
1478
+ readonly originAuthenticated: readonly InboxPrincipal[];
1479
+ /**
1480
+ * The activity as Fedify interpreted it while authenticating it, in
1481
+ * expanded JSON-LD. Typed vocabulary objects omit some values, such as
1482
+ * blank node references and values they cannot decode, so authorization
1483
+ * that has to count every value of a property, e.g., that a property has
1484
+ * exactly one value, should inspect this instead. It is deeply frozen.
1485
+ * It is empty when there is no evidence.
1486
+ */
1487
+ readonly expandedActivity: readonly Record<string, unknown>[];
1488
+ /**
1489
+ * The base URL the activity was decoded with, e.g., the URL an activity
1490
+ * fetched by `Context.routeActivity()` was finally retrieved from, or
1491
+ * `null` if none was given. Relative IRIs left in
1492
+ * {@link expandedActivity} resolve against it, or against the activity's
1493
+ * own `@id` if it is `null`, as the decoder resolves them.
1494
+ */
1495
+ readonly baseUrl: URL | null;
1496
+ }
1497
+ /**
1498
+ * A {@link InboxPrincipal} as stored in a queued message.
1499
+ * @internal
1500
+ */
1501
+ interface SerializedInboxPrincipal {
1502
+ readonly kind: InboxPrincipal["kind"];
1503
+ readonly id: string;
1504
+ readonly mechanism: InboxPrincipal["mechanism"];
1505
+ readonly keyId: string | null;
1506
+ }
1507
+ /**
1508
+ * The principals of an {@link InboxAuthenticationEvidence} as stored in
1509
+ * a queued message. The expanded activity and the base URL are restored
1510
+ * from the queued verified view instead.
1511
+ * @internal
1512
+ */
1513
+ interface SerializedInboxAuthentication {
1514
+ readonly verified: boolean;
1515
+ readonly requestSigners: readonly SerializedInboxPrincipal[];
1516
+ readonly documentSigners: readonly SerializedInboxPrincipal[];
1517
+ readonly originAuthenticated: readonly SerializedInboxPrincipal[];
1518
+ }
1519
+ //#endregion
1520
+ //#region src/sig/ld.d.ts
1521
+ /**
1522
+ * How a context loader marked by {@link markEffectiveContextLoader} already
1523
+ * resolves contexts.
1524
+ * @internal
1525
+ */
1526
+ type EffectiveContextLoaderPolicy = "standard" | "normalization";
1527
+ //#endregion
1528
+ //#region src/federation/inbox-view.d.ts
1529
+ /**
1530
+ * A {@link ContextSnapshotEntry} as stored in a queued message. A response
1531
+ * that equals the one Fedify bundles for the URL is stored as a reference to
1532
+ * the bundled context, along with its hash for diagnostics.
1533
+ * @internal
1534
+ */
1535
+ type SerializedContextSnapshotEntry = {
1536
+ readonly policy: EffectiveContextLoaderPolicy;
1537
+ readonly url: string; /** The SHA-256 of the canonical JSON of the whole response. */
1538
+ readonly sha256: string;
1539
+ } & ({
1540
+ readonly bundled: true;
1541
+ } | {
1542
+ readonly response: RemoteDocument;
1543
+ });
1544
+ /**
1545
+ * The interpretation of an incoming activity that Fedify authenticated, as
1546
+ * stored in a queued message. The document itself is not copied; it is
1547
+ * a field of the message.
1548
+ * @internal
1549
+ */
1550
+ interface SerializedVerifiedActivityView {
1551
+ /** The format version. */
1552
+ readonly version: 1;
1553
+ /** The context loader policy the activity was parsed with. */
1554
+ readonly policy: EffectiveContextLoaderPolicy;
1555
+ /**
1556
+ * Which document the activity was parsed from, with its Linked Data
1557
+ * Signature detached: the `activity` of the message, i.e., the payload as
1558
+ * received, or its `normalizedActivity`, i.e., the payload compacted for
1559
+ * Linked Data Signatures.
1560
+ */
1561
+ readonly source: "activity" | "normalizedActivity";
1562
+ /** The `baseUrl` option the activity was parsed with. */
1563
+ readonly baseUrl: string | null;
1564
+ /** The context responses the interpretation used. */
1565
+ readonly contexts: readonly SerializedContextSnapshotEntry[];
1566
+ /** The digest of the expanded activity. */
1567
+ readonly expandedDigest: string;
1568
+ }
1569
+ //#endregion
1352
1570
  //#region src/federation/retry.d.ts
1353
1571
  /**
1354
1572
  * The context passed to a {@link RetryPolicy} callback.
@@ -1683,6 +1901,21 @@ interface InboxMessage {
1683
1901
  * @internal
1684
1902
  */
1685
1903
  readonly ldSignatureVerified?: boolean;
1904
+ /**
1905
+ * The interpretation of the activity that Fedify authenticated, i.e., which
1906
+ * document it was parsed from and every JSON-LD context response the parse
1907
+ * used, so that the worker and its retries dispatch the very same
1908
+ * interpretation without loading contexts again. Absent in messages queued
1909
+ * by older Fedify versions.
1910
+ * @internal
1911
+ */
1912
+ readonly verifiedView?: SerializedVerifiedActivityView;
1913
+ /**
1914
+ * Who Fedify authenticated for the activity. Absent in messages queued by
1915
+ * older Fedify versions.
1916
+ * @internal
1917
+ */
1918
+ readonly authentication?: SerializedInboxAuthentication;
1686
1919
  readonly started: string;
1687
1920
  readonly attempt: number;
1688
1921
  readonly identifier: string | null;
@@ -3034,11 +3267,27 @@ interface InboxListenerSetters<TContextData> {
3034
3267
  /**
3035
3268
  * Registers a listener for a specific incoming activity type.
3036
3269
  *
3270
+ * @example
3271
+ * ``` typescript
3272
+ * federation
3273
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
3274
+ * // Mastodon sends FeatureRequest activities without an actor:
3275
+ * .on(FeatureRequest, async (ctx, request) => {
3276
+ * const verification = await featureInteraction.verifyRequest(ctx, {
3277
+ * request,
3278
+ * authentication: ctx.authentication,
3279
+ * });
3280
+ * // ...
3281
+ * }, { allowActorless: true });
3282
+ * ```
3283
+ *
3037
3284
  * @param type A subclass of {@link Activity} to listen to.
3038
3285
  * @param listener A callback to handle an incoming activity.
3286
+ * @param options Options for the listener. See
3287
+ * {@link InboxListenerOptions}. (Since 2.5.0.)
3039
3288
  * @returns The setters object so that settings can be chained.
3040
3289
  */
3041
- on<TActivity extends Activity>(type: new (...args: any[]) => TActivity, listener: InboxListener<TContextData, TActivity>): InboxListenerSetters<TContextData>;
3290
+ on<TActivity extends Activity>(type: new (...args: any[]) => TActivity, listener: InboxListener<TContextData, TActivity>, options?: InboxListenerOptions): InboxListenerSetters<TContextData>;
3042
3291
  /**
3043
3292
  * Registers an error handler for inbox listeners. Any exceptions thrown
3044
3293
  * from the listeners are caught and passed to this handler.
@@ -4566,6 +4815,17 @@ interface InboxContext<TContextData> extends Context<TContextData> {
4566
4815
  * @since 1.2.0
4567
4816
  */
4568
4817
  readonly recipient: string | null;
4818
+ /**
4819
+ * Who Fedify authenticated for the incoming activity, independently of its
4820
+ * declared `actor`, and the interpretation of the activity that was
4821
+ * authenticated. It is preserved through the inbox queue and its retries.
4822
+ *
4823
+ * Use it with {@link isActorAuthenticated} to authorize requests that do
4824
+ * not declare an `actor`, which reach a listener only if it is registered
4825
+ * with the `allowActorless` option.
4826
+ * @since 2.5.0
4827
+ */
4828
+ readonly authentication: InboxAuthenticationEvidence;
4569
4829
  /**
4570
4830
  * Creates a new context with the same properties as this one,
4571
4831
  * but with the given data.
@@ -2,8 +2,8 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { r as testDefinitions } from "../dist-DJQM4u7i.mjs";
5
- import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-8Tu9kea0.mjs";
5
+ import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-COHd1Rsh.mjs";
6
6
  import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
7
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, r as envelopeSchema, t as MockQueue } from "../tasks-IG3pHfvZ.mjs";
8
- import "../testing-t8xDeJZ5.mjs";
8
+ import "../testing-Dbr21vZk.mjs";
9
9
  export { ManualClockKvStore, MockQueue, baseOptions, createInboxContext, createOutboxContext, createRequestContext, envelopeSchema, makeSchema, numberSchema, stringSchema, testDefinitions };
@@ -2,7 +2,7 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import "./dist-DJQM4u7i.mjs";
5
- import "./context-8Tu9kea0.mjs";
5
+ import "./context-COHd1Rsh.mjs";
6
6
  import "./kv-DBRWVx3Q.mjs";
7
7
  import "./tasks-IG3pHfvZ.mjs";
8
8
  export {};
@@ -5,9 +5,9 @@ import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
8
- import { f as verifyRequest } from "../http-BUJ5SFh6.mjs";
8
+ import { f as verifyRequest } from "../http-BZkyQIkb.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-DIJNHW-Y.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-B-0nqOB1.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-CpsDyqVN.mjs";
11
11
  import { configure, reset } from "@logtape/logtape";
12
12
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
13
13
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as kvCache, t as MockKvStore } from "../kv-cache-D_m3A241.mjs";
4
+ import { n as kvCache, t as MockKvStore } from "../kv-cache-CTQSfkmp.mjs";
5
5
  import { preloadedContexts } from "@fedify/vocab-runtime";
6
6
  import { deepStrictEqual, throws } from "node:assert";
7
7
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_kv_cache = require("../kv-cache-kkBB0vbI.cjs");
3
+ const require_kv_cache = require("../kv-cache-CjgCOz3i.cjs");
4
4
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
5
5
  exports.kvCache = require_kv_cache.kvCache;
package/dist/utils/mod.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-7015lbrx.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-U7uz5nx4.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "2.5.0-dev.2446+2014cb03",
3
+ "version": "2.5.0-dev.2448+756933d0",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -151,10 +151,10 @@
151
151
  "structured-field-values": "^2.0.4",
152
152
  "temporal-polyfill": "^1.0.1",
153
153
  "urlpattern-polyfill": "^10.1.0",
154
- "@fedify/uri-template": "2.5.0-dev.2446+2014cb03",
155
- "@fedify/vocab-runtime": "2.5.0-dev.2446+2014cb03",
156
- "@fedify/vocab": "2.5.0-dev.2446+2014cb03",
157
- "@fedify/webfinger": "2.5.0-dev.2446+2014cb03"
154
+ "@fedify/vocab": "2.5.0-dev.2448+756933d0",
155
+ "@fedify/webfinger": "2.5.0-dev.2448+756933d0",
156
+ "@fedify/uri-template": "2.5.0-dev.2448+756933d0",
157
+ "@fedify/vocab-runtime": "2.5.0-dev.2448+756933d0"
158
158
  },
159
159
  "devDependencies": {
160
160
  "@std/assert": "npm:@jsr/std__assert@^0.226.0",
@@ -166,8 +166,8 @@
166
166
  "tsx": "^4.21.0",
167
167
  "typescript": "^6.0.0",
168
168
  "wrangler": "^4.17.0",
169
- "@fedify/fixture": "2.0.0",
170
- "@fedify/vocab-tools": "^2.5.0-dev.2446+2014cb03"
169
+ "@fedify/vocab-tools": "^2.5.0-dev.2448+756933d0",
170
+ "@fedify/fixture": "2.0.0"
171
171
  },
172
172
  "scripts": {
173
173
  "build:self": "tsdown",
@@ -1,188 +0,0 @@
1
- import { Temporal } from "temporal-polyfill";
2
- import "urlpattern-polyfill";
3
- globalThis.addEventListener = () => {};
4
- import { t as getAuthenticationOrigin } from "./portable-key-id-Ca30-hOa.mjs";
5
- import { n as version, t as name } from "./deno-eNpfj4oR.mjs";
6
- import { g as recordInboxActivity, n as getDurationMs, r as getFederationMetrics } from "./metrics-D3LHk6Z4.mjs";
7
- import { getLogger } from "@logtape/logtape";
8
- import { getTypeId } from "@fedify/vocab";
9
- import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
10
- //#region src/federation/inbox.ts
11
- /**
12
- * Checks whether an activity's ID and all of its actor IDs have the same
13
- * [FEP-fe34] origin. Activities without an ID have no origin to authenticate.
14
- *
15
- * Origins are compared by `getAuthenticationOrigin()`, so a portable activity
16
- * and its portable actors, including compatible identifiers on any gateway,
17
- * are compared by their DIDs. An ID that has no supported origin, such as an
18
- * opaque URN, never matches.
19
- *
20
- * The caller is responsible for rejecting a missing primary actor ID before
21
- * relying on this check.
22
- *
23
- * [FEP-fe34]: https://w3id.org/fep/fe34
24
- * @internal
25
- */
26
- function actorsHaveSameOrigin(activity) {
27
- if (activity.id == null) return true;
28
- const origin = getAuthenticationOrigin(activity.id);
29
- return origin != null && activity.actorIds.every((actor) => getAuthenticationOrigin(actor) === origin);
30
- }
31
- async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
32
- const logger = getLogger([
33
- "fedify",
34
- "federation",
35
- "inbox"
36
- ]);
37
- if (activity.actorId == null) {
38
- logger.error("Missing actor.", { activity: json });
39
- span.setStatus({
40
- code: SpanStatusCode.ERROR,
41
- message: "Missing actor."
42
- });
43
- recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
44
- return "missingActor";
45
- }
46
- let cacheKey = null;
47
- if (activity.id != null) {
48
- const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
49
- const strategy = idempotencyStrategy ?? "per-inbox";
50
- let keyString;
51
- if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
52
- else switch (strategy) {
53
- case "global":
54
- keyString = activity.id.href;
55
- break;
56
- case "per-origin":
57
- keyString = `${ctx.origin}\n${activity.id.href}`;
58
- break;
59
- case "per-inbox":
60
- keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
61
- break;
62
- default: keyString = `${ctx.origin}\n${activity.id.href}`;
63
- }
64
- if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
65
- }
66
- if (cacheKey != null) {
67
- if (await kv.get(cacheKey) === true) {
68
- logger.debug("Activity {activityId} has already been processed.", {
69
- activityId: activity.id?.href,
70
- activity: json,
71
- recipient
72
- });
73
- span.setStatus({
74
- code: SpanStatusCode.UNSET,
75
- message: `Activity ${activity.id?.href} has already been processed.`
76
- });
77
- recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
78
- return "alreadyProcessed";
79
- }
80
- }
81
- span.setAttribute("activitypub.actor.id", activity.actorId.href);
82
- if (queue != null) {
83
- const carrier = {};
84
- propagation.inject(context.active(), carrier);
85
- try {
86
- await queue.enqueue({
87
- type: "inbox",
88
- id: crypto.randomUUID(),
89
- baseUrl: ctx.origin,
90
- activity: originalJson ?? json,
91
- ...normalizedActivity == null ? {} : { normalizedActivity },
92
- ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
93
- identifier: recipient,
94
- attempt: 0,
95
- started: (/* @__PURE__ */ new Date()).toISOString(),
96
- traceContext: carrier
97
- });
98
- } catch (error) {
99
- logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
100
- error,
101
- activityId: activity.id?.href,
102
- activity: json,
103
- recipient
104
- });
105
- span.setStatus({
106
- code: SpanStatusCode.ERROR,
107
- message: `Failed to enqueue the incoming activity ${activity.id?.href}.`
108
- });
109
- throw error;
110
- }
111
- getFederationMetrics(meterProvider).recordQueueTaskEnqueued({
112
- role: "inbox",
113
- queue,
114
- activityType: getTypeId(activity).href
115
- }, 0);
116
- recordInboxActivity(meterProvider, "queued", getTypeId(activity).href);
117
- logger.info("Activity {activityId} is enqueued.", {
118
- activityId: activity.id?.href,
119
- activity: json,
120
- recipient
121
- });
122
- return "enqueued";
123
- }
124
- tracerProvider = tracerProvider ?? trace.getTracerProvider();
125
- return await tracerProvider.getTracer(name, version).startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
126
- const dispatched = inboxListeners?.dispatchWithClass(activity);
127
- if (dispatched == null) {
128
- logger.error("Unsupported activity type:\n{activity}", {
129
- activity: json,
130
- recipient
131
- });
132
- span.setStatus({
133
- code: SpanStatusCode.UNSET,
134
- message: `Unsupported activity type: ${getTypeId(activity).href}`
135
- });
136
- recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
137
- span.end();
138
- return "unsupportedActivity";
139
- }
140
- const { class: cls, listener } = dispatched;
141
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
142
- try {
143
- const activityType = getTypeId(activity).href;
144
- const started = performance.now();
145
- try {
146
- const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
147
- await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
148
- } finally {
149
- getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
150
- }
151
- } catch (error) {
152
- try {
153
- await inboxErrorHandler?.(ctx, error);
154
- } catch (error) {
155
- logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
156
- error,
157
- activityId: activity.id?.href,
158
- activity: json,
159
- recipient
160
- });
161
- }
162
- logger.error("Failed to process the incoming activity {activityId}:\n{error}", {
163
- error,
164
- activityId: activity.id?.href,
165
- activity: json,
166
- recipient
167
- });
168
- span.setStatus({
169
- code: SpanStatusCode.ERROR,
170
- message: String(error)
171
- });
172
- recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
173
- span.end();
174
- return "error";
175
- }
176
- recordInboxActivity(meterProvider, "processed", getTypeId(activity).href);
177
- if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
178
- logger.info("Activity {activityId} has been processed.", {
179
- activityId: activity.id?.href,
180
- activity: json,
181
- recipient
182
- });
183
- span.end();
184
- return "success";
185
- });
186
- }
187
- //#endregion
188
- export { routeActivity as n, actorsHaveSameOrigin as t };