@fedify/fedify 2.1.27 → 2.1.29

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 (60) hide show
  1. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-WyYSTDDX.mjs} +2 -20
  2. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-D4DNWrux.mjs} +19 -1
  3. package/dist/{builder-CKQkP3a2.mjs → builder-Mg2G3qj8.mjs} +3 -3
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{deno-BR2CGENT.mjs → deno-BiYv5RT5.mjs} +1 -1
  6. package/dist/{docloader-BDHKVMoV.mjs → docloader-DrxXBFL8.mjs} +4 -2
  7. package/dist/federation/builder.test.mjs +3 -3
  8. package/dist/federation/collection.test.mjs +1 -1
  9. package/dist/federation/handler.test.mjs +262 -9
  10. package/dist/federation/idempotency.test.mjs +3 -3
  11. package/dist/federation/inbox.test.mjs +25 -2
  12. package/dist/federation/kv.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +128 -10
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.js +1 -1
  16. package/dist/federation/mq.test.mjs +2 -2
  17. package/dist/federation/negotiation.test.mjs +2 -2
  18. package/dist/federation/retry.test.mjs +1 -1
  19. package/dist/federation/router.test.mjs +2 -3
  20. package/dist/federation/send.test.mjs +7 -6
  21. package/dist/federation/temporal.test.mjs +3 -3
  22. package/dist/federation/webfinger.test.mjs +2 -2
  23. package/dist/{http-DQVY5EMT.js → http-B3rSb2GQ.js} +104 -10
  24. package/dist/{http-QzHm1vsW.mjs → http-C1ga9WQu.mjs} +2 -2
  25. package/dist/{http-CsQRvInz.cjs → http-Cginz7L0.cjs} +103 -9
  26. package/dist/{inbox-DEXoNgGa.mjs → inbox-BWzL0xMU.mjs} +23 -10
  27. package/dist/{key-CDIMbgsc.mjs → key-O-ATwIGS.mjs} +104 -10
  28. package/dist/{kv-cache-cTsPZ49Z.js → kv-cache-8Kg2CHiD.js} +3 -1
  29. package/dist/{kv-cache-FfmHQljR.cjs → kv-cache-BdylAE9w.cjs} +3 -1
  30. package/dist/{ld-BpkaBh2H.mjs → ld-C-fCGM-z.mjs} +2 -2
  31. package/dist/{middleware-DwEVn3gK.js → middleware-41MkquTV.js} +95 -25
  32. package/dist/{middleware-DVaturw-.mjs → middleware-C9UCWOqw.mjs} +82 -25
  33. package/dist/{middleware-Bla8jBXk.cjs → middleware-CVRFS7PZ.cjs} +96 -26
  34. package/dist/{middleware-C9vVE-6R.mjs → middleware-CxgFiKI3.mjs} +1 -1
  35. package/dist/{middleware-Ddrg18eD.cjs → middleware-VvrqPwHL.cjs} +1 -1
  36. package/dist/mod.cjs +4 -4
  37. package/dist/mod.js +4 -4
  38. package/dist/nodeinfo/client.test.mjs +1 -1
  39. package/dist/nodeinfo/handler.test.mjs +2 -2
  40. package/dist/nodeinfo/types.test.mjs +2 -2
  41. package/dist/otel/exporter.test.mjs +1 -1
  42. package/dist/{owner-DnOwKsK6.mjs → owner-C8IVASD3.mjs} +2 -2
  43. package/dist/{proof-JfqNWc2E.cjs → proof-BbHMAkPG.cjs} +1 -1
  44. package/dist/{proof-CShmVLWt.mjs → proof-CUdcBgvg.mjs} +2 -2
  45. package/dist/{proof-uEay4EIq.js → proof-cGfRPTiV.js} +1 -1
  46. package/dist/{send-DCg0WblG.mjs → send-BoV4dyVO.mjs} +2 -2
  47. package/dist/sig/http.test.mjs +5 -5
  48. package/dist/sig/key.test.mjs +200 -6
  49. package/dist/sig/ld.test.mjs +4 -4
  50. package/dist/sig/mod.cjs +2 -2
  51. package/dist/sig/mod.js +2 -2
  52. package/dist/sig/owner.test.mjs +3 -3
  53. package/dist/sig/proof.test.mjs +3 -3
  54. package/dist/{std__assert-BUOJXi-Z.mjs → std__assert-DflMZha5.mjs} +2 -2
  55. package/dist/{temporal-C96jXvaM.mjs → temporal-BJlnku_d.mjs} +1 -1
  56. package/dist/utils/docloader.test.mjs +114 -39
  57. package/dist/utils/mod.cjs +1 -1
  58. package/dist/utils/mod.js +1 -1
  59. package/package.json +6 -6
  60. /package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-BkxW5QJI.mjs} +0 -0
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-CsQRvInz.cjs");
5
+ const require_http = require("./http-Cginz7L0.cjs");
6
6
  const require_types = require("./types-DRPjmHVV.cjs");
7
- const require_proof = require("./proof-JfqNWc2E.cjs");
8
- const require_kv_cache = require("./kv-cache-FfmHQljR.cjs");
7
+ const require_proof = require("./proof-BbHMAkPG.cjs");
8
+ const require_kv_cache = require("./kv-cache-BdylAE9w.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -53,12 +53,33 @@ var InboxListenerSet = class InboxListenerSet {
53
53
  return this.dispatchWithClass(activity)?.listener ?? null;
54
54
  }
55
55
  };
56
+ /**
57
+ * Checks whether an activity's ID and all of its actor IDs have the same
58
+ * non-opaque origin. Activities without an ID have no origin to authenticate.
59
+ *
60
+ * The caller is responsible for rejecting a missing primary actor ID before
61
+ * relying on this check.
62
+ * @internal
63
+ */
64
+ function actorsHaveSameOrigin(activity) {
65
+ if (activity.id == null) return true;
66
+ const origin = activity.id.origin;
67
+ return origin !== "null" && activity.actorIds.every((actor) => actor.origin === origin);
68
+ }
56
69
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
57
70
  const logger = (0, _logtape_logtape.getLogger)([
58
71
  "fedify",
59
72
  "federation",
60
73
  "inbox"
61
74
  ]);
75
+ if (activity.actorId == null) {
76
+ logger.error("Missing actor.", { activity: json });
77
+ span.setStatus({
78
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
79
+ message: "Missing actor."
80
+ });
81
+ return "missingActor";
82
+ }
62
83
  let cacheKey = null;
63
84
  if (activity.id != null) {
64
85
  const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href);
@@ -93,14 +114,6 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
93
114
  return "alreadyProcessed";
94
115
  }
95
116
  }
96
- if (activity.actorId == null) {
97
- logger.error("Missing actor.", { activity: json });
98
- span.setStatus({
99
- code: _opentelemetry_api.SpanStatusCode.ERROR,
100
- message: "Missing actor."
101
- });
102
- return "missingActor";
103
- }
104
117
  span.setAttribute("activitypub.actor.id", activity.actorId.href);
105
118
  if (queue != null) {
106
119
  const carrier = {};
@@ -337,7 +350,7 @@ var FederationBuilderImpl = class {
337
350
  this.collectionTypeIds = {};
338
351
  }
339
352
  async build(options) {
340
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-Ddrg18eD.cjs"));
353
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-VvrqPwHL.cjs"));
341
354
  const f = new FederationImpl(options);
342
355
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
343
356
  f.router = this.router.clone();
@@ -1799,6 +1812,36 @@ async function handleInboxInternal(request, parameters, span) {
1799
1812
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1800
1813
  }
1801
1814
  }
1815
+ if (activity.actorId == null) {
1816
+ logger.error("Missing actor.", {
1817
+ activity: json,
1818
+ recipient
1819
+ });
1820
+ span.setStatus({
1821
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1822
+ message: "Missing actor."
1823
+ });
1824
+ return new Response("Missing actor.", {
1825
+ status: 400,
1826
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1827
+ });
1828
+ }
1829
+ if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
1830
+ logger.error("The activity ({activityId}) and its actors do not have the same origin.", {
1831
+ activity: json,
1832
+ recipient,
1833
+ activityId: activity.id.href,
1834
+ actorIds: activity.actorIds.map((actor) => actor.href)
1835
+ });
1836
+ span.setStatus({
1837
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1838
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
1839
+ });
1840
+ return new Response("The activity and its actors do not have the same origin.", {
1841
+ status: 401,
1842
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1843
+ });
1844
+ }
1802
1845
  const routeResult = await routeActivity({
1803
1846
  context: ctx,
1804
1847
  json,
@@ -3310,6 +3353,33 @@ var FederationImpl = class extends FederationBuilderImpl {
3310
3353
  activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
3311
3354
  span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
3312
3355
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3356
+ if (activity.actorId == null) {
3357
+ logger.error("Missing actor in queued incoming activity.", {
3358
+ activityId: activity.id?.href,
3359
+ activity: message.activity,
3360
+ recipient: message.identifier
3361
+ });
3362
+ span.setStatus({
3363
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3364
+ message: "Missing actor."
3365
+ });
3366
+ span.end();
3367
+ return;
3368
+ }
3369
+ if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
3370
+ logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
3371
+ activityId: activity.id.href,
3372
+ actorIds: activity.actorIds.map((actor) => actor.href),
3373
+ activity: message.activity,
3374
+ recipient: message.identifier
3375
+ });
3376
+ span.setStatus({
3377
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3378
+ message: `The activity (${activity.id.href}) and its actors do not have the same origin.`
3379
+ });
3380
+ span.end();
3381
+ return;
3382
+ }
3313
3383
  cacheKey = activity.id == null ? null : [
3314
3384
  ...this.kvPrefixes.activityIdempotence,
3315
3385
  context.origin,
@@ -4422,20 +4492,6 @@ var ContextImpl = class ContextImpl {
4422
4492
  activity: await fetched.toJsonLd({ contextLoader })
4423
4493
  });
4424
4494
  return false;
4425
- } else if (fetched.actorIds.length < 1) {
4426
- logger.debug("Fetched activity object is missing an actor; unable to verify.", {
4427
- recipient,
4428
- activity: await fetched.toJsonLd({ contextLoader })
4429
- });
4430
- return false;
4431
- }
4432
- const activityId = fetched.id;
4433
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
4434
- logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
4435
- recipient,
4436
- activity: await fetched.toJsonLd({ contextLoader })
4437
- });
4438
- return false;
4439
4495
  }
4440
4496
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
4441
4497
  activity = fetched;
@@ -4444,6 +4500,20 @@ var ContextImpl = class ContextImpl {
4444
4500
  recipient,
4445
4501
  activity: json
4446
4502
  });
4503
+ if (activity.actorId == null) {
4504
+ logger.debug("Activity is missing an actor; unable to verify.", {
4505
+ recipient,
4506
+ activity: json
4507
+ });
4508
+ return false;
4509
+ }
4510
+ if (activity.id != null && !actorsHaveSameOrigin(activity)) {
4511
+ logger.debug("Activity has actors from different origins; unable to verify.", {
4512
+ recipient,
4513
+ activity: json
4514
+ });
4515
+ return false;
4516
+ }
4447
4517
  const routeResult = await routeActivity({
4448
4518
  context: this,
4449
4519
  json,
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-DVaturw-.mjs";
4
+ import { n as FederationImpl } from "./middleware-C9UCWOqw.mjs";
5
5
  export { FederationImpl };
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-Bla8jBXk.cjs");
3
+ const require_middleware = require("./middleware-CVRFS7PZ.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
package/dist/mod.cjs CHANGED
@@ -4,11 +4,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("./chunk-DDcVe30Y.cjs");
5
5
  const require_transformers = require("./transformers-NeAONrAq.cjs");
6
6
  require("./compat/mod.cjs");
7
- const require_http = require("./http-CsQRvInz.cjs");
8
- const require_middleware = require("./middleware-Bla8jBXk.cjs");
7
+ const require_http = require("./http-Cginz7L0.cjs");
8
+ const require_middleware = require("./middleware-CVRFS7PZ.cjs");
9
9
  const require_types = require("./types-DRPjmHVV.cjs");
10
- const require_proof = require("./proof-JfqNWc2E.cjs");
11
- const require_kv_cache = require("./kv-cache-FfmHQljR.cjs");
10
+ const require_proof = require("./proof-BbHMAkPG.cjs");
11
+ const require_kv_cache = require("./kv-cache-BdylAE9w.cjs");
12
12
  const require_federation_mod = require("./federation/mod.cjs");
13
13
  require("./nodeinfo/mod.cjs");
14
14
  require("./runtime/mod.cjs");
package/dist/mod.js CHANGED
@@ -3,11 +3,11 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import "./chunk-CRNNMoPX.js";
4
4
  import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "./transformers-BGMIq1cs.js";
5
5
  import "./compat/mod.js";
6
- import { _ as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKey, d as importJwk, g as parseAcceptSignature, h as fulfillAcceptSignature, i as verifyRequest, l as fetchKeyDetailed, m as formatAcceptSignature, o as exportJwk, r as signRequest, u as generateCryptoKeyPair } from "./http-DQVY5EMT.js";
7
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-DwEVn3gK.js";
6
+ import { _ as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKey, d as importJwk, g as parseAcceptSignature, h as fulfillAcceptSignature, i as verifyRequest, l as fetchKeyDetailed, m as formatAcceptSignature, o as exportJwk, r as signRequest, u as generateCryptoKeyPair } from "./http-B3rSb2GQ.js";
7
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-41MkquTV.js";
8
8
  import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "./types-miAYfiGO.js";
9
- import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "./proof-uEay4EIq.js";
10
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-cTsPZ49Z.js";
9
+ import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "./proof-cGfRPTiV.js";
10
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-8Kg2CHiD.js";
11
11
  import { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue } from "./federation/mod.js";
12
12
  import "./nodeinfo/mod.js";
13
13
  import "./runtime/mod.js";
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-BUOJXi-Z.mjs";
6
+ import "../std__assert-DflMZha5.mjs";
7
7
  import { n as assertInstanceOf } from "../assert-BhQd8QHP.mjs";
8
8
  import { a as parseProtocol, c as parseUsage, i as parseOutboundService, n as parseInboundService, o as parseServices, r as parseNodeInfo, s as parseSoftware, t as getNodeInfo } from "../client-CjDMsn4a.mjs";
9
9
  import { test } from "@fedify/fixture";
@@ -3,9 +3,9 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as createRequestContext } from "../context-Juj6bdHC.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-BUOJXi-Z.mjs";
6
+ import "../std__assert-DflMZha5.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-HvthQAHr.mjs";
8
- import { a as createFederation, h as handleNodeInfoJrd, m as handleNodeInfo } from "../middleware-DVaturw-.mjs";
8
+ import { a as createFederation, h as handleNodeInfoJrd, m as handleNodeInfo } from "../middleware-C9UCWOqw.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  //#region src/nodeinfo/handler.test.ts
11
11
  test("handleNodeInfo()", async () => {
@@ -2,8 +2,8 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-BUOJXi-Z.mjs";
6
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
5
+ import "../std__assert-DflMZha5.mjs";
6
+ import { t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
7
7
  import { t as nodeInfoToJson } from "../types-DCP0WLdt.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/nodeinfo/types.test.ts
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-BUOJXi-Z.mjs";
5
+ import "../std__assert-DflMZha5.mjs";
6
6
  import { t as MemoryKvStore } from "../kv-HvthQAHr.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  import { SpanKind, SpanStatusCode, TraceFlags } from "@opentelemetry/api";
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BR2CGENT.mjs";
5
- import { c as verifyKeyOwnership, n as fetchActorDocument, t as exportJwk } from "./key-CDIMbgsc.mjs";
4
+ import { n as version, t as name } from "./deno-BiYv5RT5.mjs";
5
+ import { c as verifyKeyOwnership, n as fetchActorDocument, t as exportJwk } from "./key-O-ATwIGS.mjs";
6
6
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-CsQRvInz.cjs");
4
+ const require_http = require("./http-Cginz7L0.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let _fedify_vocab = require("@fedify/vocab");
7
7
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -1,8 +1,8 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BR2CGENT.mjs";
5
- import { r as fetchKey, s as validateCryptoKey } from "./key-CDIMbgsc.mjs";
4
+ import { n as version, t as name } from "./deno-BiYv5RT5.mjs";
5
+ import { r as fetchKey, s as validateCryptoKey } from "./key-O-ATwIGS.mjs";
6
6
  import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getLogger } from "@logtape/logtape";
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { c as fetchKey, f as validateCryptoKey, o as exportJwk, p as verifyKeyOwnership, s as fetchActorDocument, v as name, y as version } from "./http-DQVY5EMT.js";
3
+ import { c as fetchKey, f as validateCryptoKey, o as exportJwk, p as verifyKeyOwnership, s as fetchActorDocument, v as name, y as version } from "./http-B3rSb2GQ.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { Activity, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1, getTypeId, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-BR2CGENT.mjs";
5
- import { n as doubleKnock } from "./http-QzHm1vsW.mjs";
4
+ import { n as version, t as name } from "./deno-BiYv5RT5.mjs";
5
+ import { n as doubleKnock } from "./http-C1ga9WQu.mjs";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { FetchError, UrlError, validatePublicUrl } from "@fedify/vocab-runtime";
8
8
  import { getLogger } from "@logtape/logtape";
@@ -3,12 +3,12 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import { i as assertExists, t as assertStringIncludes } from "../std__assert-BUOJXi-Z.mjs";
7
- import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
6
+ import { i as assertExists, t as assertStringIncludes } from "../std__assert-DflMZha5.mjs";
7
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
8
8
  import { t as assert } from "../assert-BhQd8QHP.mjs";
9
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
10
- import { t as exportJwk } from "../key-CDIMbgsc.mjs";
11
- import { a as parseRfc9421Signature, c as timingSafeEqual, i as formatRfc9421SignatureParameters, l as verifyRequest, n as doubleKnock, o as parseRfc9421SignatureInput, r as formatRfc9421Signature, s as signRequest, t as createRfc9421SignatureBase, u as verifyRequestDetailed } from "../http-QzHm1vsW.mjs";
9
+ import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
10
+ import { t as exportJwk } from "../key-O-ATwIGS.mjs";
11
+ import { a as parseRfc9421Signature, c as timingSafeEqual, i as formatRfc9421SignatureParameters, l as verifyRequest, n as doubleKnock, o as parseRfc9421SignatureInput, r as formatRfc9421Signature, s as signRequest, t as createRfc9421SignatureBase, u as verifyRequestDetailed } from "../http-C1ga9WQu.mjs";
12
12
  import { i as rsaPrivateKey2, l as rsaPublicKey5, o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-CtZLJq76.mjs";
13
13
  import { t as KvKeyCache } from "../keycache-RFlZObU3.mjs";
14
14
  import { t as MemoryKvStore } from "../kv-HvthQAHr.mjs";
@@ -2,15 +2,16 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-BUOJXi-Z.mjs";
6
- import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
5
+ import { n as assertStrictEquals } from "../std__assert-DflMZha5.mjs";
6
+ import { t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
7
7
  import { t as assert } from "../assert-BhQd8QHP.mjs";
8
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
9
- import { a as generateCryptoKeyPair, i as fetchKeyDetailed, o as importJwk, r as fetchKey, s as validateCryptoKey, t as exportJwk } from "../key-CDIMbgsc.mjs";
8
+ import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
9
+ import { a as generateCryptoKeyPair, i as fetchKeyDetailed, o as importJwk, r as fetchKey, s as validateCryptoKey, t as exportJwk } from "../key-O-ATwIGS.mjs";
10
10
  import { c as rsaPublicKey3, i as rsaPrivateKey2, o as rsaPublicKey1, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CtZLJq76.mjs";
11
+ import { g as wrapContextLoaderForJsonLd } from "../ld-C-fCGM-z.mjs";
11
12
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
12
- import { CryptographicKey, Multikey } from "@fedify/vocab";
13
- import { FetchError } from "@fedify/vocab-runtime";
13
+ import { CryptographicKey, Multikey, Person } from "@fedify/vocab";
14
+ import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
14
15
  //#region src/sig/key.test.ts
15
16
  test("validateCryptoKey()", async () => {
16
17
  const pkcs1v15 = await crypto.subtle.generateKey({
@@ -399,6 +400,199 @@ test("fetchKey() returns null for a malformed actor publicKey", async () => {
399
400
  cached: true
400
401
  });
401
402
  });
403
+ for (const cls of [CryptographicKey, Multikey]) for (const location of [
404
+ "actor",
405
+ "key",
406
+ "owner"
407
+ ]) test(`fetchKey() handles JSON-LD context failures in ${cls.name} ${location} documents`, async (t) => {
408
+ const actorId = new URL("https://example.com/context-actor");
409
+ const keyId = new URL(`${actorId.href}#key`);
410
+ const key = cls === CryptographicKey ? rsaPublicKey1.clone({
411
+ id: keyId,
412
+ owner: actorId
413
+ }) : ed25519Multikey.clone({
414
+ id: keyId,
415
+ controller: actorId
416
+ });
417
+ const actorDocument = await new Person({
418
+ id: actorId,
419
+ ...key instanceof CryptographicKey ? { publicKeys: [key] } : { assertionMethods: [key] }
420
+ }).toJsonLd({ contextLoader: mockDocumentLoader });
421
+ const keyDocument = await key.toJsonLd({ contextLoader: mockDocumentLoader });
422
+ const contextUrl = `https://example.com/context/${cls.name}/${location}`;
423
+ const transportErrors = [
424
+ /* @__PURE__ */ new TypeError("fetch failed"),
425
+ /* @__PURE__ */ new TypeError("error reading a body from connection"),
426
+ globalThis.Object.assign(/* @__PURE__ */ new TypeError("The socket connection was closed unexpectedly."), { code: "ECONNRESET" }),
427
+ globalThis.Object.assign(/* @__PURE__ */ new TypeError("Network request failed"), { code: "ECONNRESET" }),
428
+ new TypeError("Network request failed", { cause: globalThis.Object.assign(/* @__PURE__ */ new Error("connection refused"), { code: "ECONNREFUSED" }) }),
429
+ new FetchError(contextUrl, "HTTP 503"),
430
+ new FetchError(contextUrl, "HTTP 503", new Response("unavailable", { status: 503 })),
431
+ new UrlError("DNS lookup failed", { reason: "dns" }),
432
+ globalThis.Object.assign(/* @__PURE__ */ new Error("request aborted"), { name: "AbortError" })
433
+ ];
434
+ const programmingErrors = [
435
+ /* @__PURE__ */ new Error("loader bug"),
436
+ /* @__PURE__ */ new ReferenceError("loader bug"),
437
+ /* @__PURE__ */ new TypeError("loader bug"),
438
+ "loader bug",
439
+ null,
440
+ void 0
441
+ ];
442
+ const cases = [
443
+ { kind: "inline" },
444
+ {
445
+ kind: "url",
446
+ wrapped: false
447
+ },
448
+ {
449
+ kind: "url",
450
+ wrapped: true
451
+ },
452
+ {
453
+ kind: "wrapped",
454
+ error: /* @__PURE__ */ new TypeError("Invalid URL string.")
455
+ },
456
+ {
457
+ kind: "remote",
458
+ document: 123
459
+ },
460
+ {
461
+ kind: "remote",
462
+ document: "not JSON"
463
+ },
464
+ {
465
+ kind: "remote",
466
+ document: { "@context": contextUrl }
467
+ },
468
+ ...transportErrors.map((error) => ({
469
+ kind: "transport",
470
+ error
471
+ })),
472
+ ...programmingErrors.map((error) => ({
473
+ kind: "programming",
474
+ error
475
+ })),
476
+ ...location === "key" ? [{
477
+ kind: "transport",
478
+ error: /* @__PURE__ */ new TypeError("fetch failed"),
479
+ fallback: true
480
+ }, {
481
+ kind: "programming",
482
+ error: /* @__PURE__ */ new ReferenceError("fallback bug"),
483
+ fallback: true
484
+ }] : []
485
+ ];
486
+ for (const scenario of cases) {
487
+ const description = "error" in scenario ? scenario.error instanceof Error ? `${scenario.error.name}: ${scenario.error.message}` : String(scenario.error) : "document" in scenario ? JSON.stringify(scenario.document) : "invalid type mapping";
488
+ await t.step(`${scenario.kind}: ${description}${"fallback" in scenario ? " (fallback)" : ""}`, async () => {
489
+ const cache = {};
490
+ let fetchError = { error: /* @__PURE__ */ new Error("stale fetch error") };
491
+ let recovered = false;
492
+ let contextLoads = 0;
493
+ const options = {
494
+ documentLoader(resource) {
495
+ const document = structuredClone(resource === keyId.href && location !== "actor" ? keyDocument : actorDocument);
496
+ if (location === "actor" || location === "key" && resource === keyId.href || location === "owner" && resource === actorId.href) {
497
+ const context = scenario.kind === "inline" ? { broken: {
498
+ "@id": "https://example.com/ns#broken",
499
+ "@type": "not-an-absolute-iri"
500
+ } } : scenario.kind === "url" ? "http://[" : contextUrl;
501
+ document["@context"] = [document["@context"], context].flat();
502
+ }
503
+ return Promise.resolve({
504
+ contextUrl: null,
505
+ documentUrl: resource,
506
+ document
507
+ });
508
+ },
509
+ async contextLoader(resource) {
510
+ if (scenario.kind === "url" && resource === "http://[") {
511
+ const loader = getDocumentLoader();
512
+ return await (scenario.wrapped ? wrapContextLoaderForJsonLd(loader) : loader)(resource);
513
+ }
514
+ if (scenario.kind === "wrapped" && resource === contextUrl) return await wrapContextLoaderForJsonLd(() => Promise.reject(scenario.error))(resource);
515
+ if (resource !== contextUrl) return await mockDocumentLoader(resource);
516
+ contextLoads++;
517
+ if (!recovered && "error" in scenario && (!("fallback" in scenario) || contextLoads > 1)) throw scenario.error;
518
+ return {
519
+ contextUrl: null,
520
+ documentUrl: resource,
521
+ document: !recovered && "document" in scenario ? scenario.document : { "@context": {} }
522
+ };
523
+ },
524
+ keyCache: {
525
+ get: (id) => Promise.resolve(cache[id.href]),
526
+ set(id, value) {
527
+ cache[id.href] = value;
528
+ return Promise.resolve();
529
+ },
530
+ getFetchError: () => Promise.resolve(fetchError),
531
+ setFetchError(_id, error) {
532
+ fetchError = error ?? void 0;
533
+ return Promise.resolve();
534
+ }
535
+ }
536
+ };
537
+ const lookup = () => fetchKey(keyId, cls, options);
538
+ if (scenario.kind === "programming" && "error" in scenario) {
539
+ assertStrictEquals(await assertRejects(lookup), scenario.error);
540
+ assertEquals(cache, {});
541
+ assertStrictEquals(await assertRejects(() => fetchKeyDetailed(keyId, cls, options)), scenario.error);
542
+ assertEquals(cache, {});
543
+ return;
544
+ }
545
+ assertEquals(await lookup(), {
546
+ key: null,
547
+ cached: false
548
+ });
549
+ assertEquals(cache, { [keyId.href]: null });
550
+ const detailed = await fetchKeyDetailed(keyId, cls, options);
551
+ assertEquals(detailed.cached, true);
552
+ if (scenario.kind === "transport" || scenario.kind === "wrapped") {
553
+ assert(detailed.fetchError != null);
554
+ if (scenario.error instanceof FetchError && scenario.error.response != null) {
555
+ assert("status" in detailed.fetchError);
556
+ assertEquals(detailed.fetchError.status, 503);
557
+ assertEquals(await detailed.fetchError.response.text(), "unavailable");
558
+ } else {
559
+ assert("error" in detailed.fetchError);
560
+ assertStrictEquals(detailed.fetchError.error, scenario.error);
561
+ }
562
+ } else assertEquals(detailed.fetchError, void 0);
563
+ const previousLoads = contextLoads;
564
+ assertEquals(await lookup(), {
565
+ key: null,
566
+ cached: true
567
+ });
568
+ assertEquals(contextLoads, previousLoads);
569
+ delete cache[keyId.href];
570
+ fetchError = void 0;
571
+ const fresh = await fetchKeyDetailed(keyId, cls, options);
572
+ assertEquals(fresh.cached, false);
573
+ assertEquals(fresh.key, null);
574
+ if (scenario.kind === "transport" || scenario.kind === "wrapped") {
575
+ assert(fresh.fetchError != null);
576
+ if (scenario.error instanceof FetchError && scenario.error.response != null) {
577
+ assert("status" in fresh.fetchError);
578
+ assertEquals(fresh.fetchError.status, 503);
579
+ assertEquals(await fresh.fetchError.response.text(), "unavailable");
580
+ } else {
581
+ assert("error" in fresh.fetchError);
582
+ assertStrictEquals(fresh.fetchError.error, scenario.error);
583
+ }
584
+ } else assertEquals(fresh.fetchError, void 0);
585
+ if (scenario.kind === "transport") {
586
+ delete cache[keyId.href];
587
+ recovered = true;
588
+ const result = await lookup();
589
+ assertEquals(result.cached, false);
590
+ assertEquals(result.key?.id, keyId);
591
+ assert(result.key?.publicKey != null);
592
+ }
593
+ });
594
+ }
595
+ });
402
596
  test("fetchKey() rejects a key whose owner does not link back", async () => {
403
597
  const keyId = "https://attacker.example/key";
404
598
  const keyDocument = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
@@ -2,12 +2,12 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
5
+ import { r as assertFalse, t as assertThrows } from "../assert_throws-D4DNWrux.mjs";
6
6
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
7
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
8
- import { a as generateCryptoKeyPair } from "../key-CDIMbgsc.mjs";
7
+ import { t as assertRejects } from "../assert_rejects-WyYSTDDX.mjs";
8
+ import { a as generateCryptoKeyPair } from "../key-O-ATwIGS.mjs";
9
9
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CtZLJq76.mjs";
10
- import { a as compactJsonLd, d as isInvalidUrlTypeError, f as signJsonLd, h as verifySignature, i as attachSignature, m as verifyJsonLd, n as UnsafeJsonLdError, o as createSignature, s as detachSignature } from "../ld-BpkaBh2H.mjs";
10
+ import { a as compactJsonLd, d as isInvalidUrlTypeError, f as signJsonLd, h as verifySignature, i as attachSignature, m as verifyJsonLd, n as UnsafeJsonLdError, o as createSignature, s as detachSignature } from "../ld-C-fCGM-z.mjs";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  import { CryptographicKey } from "@fedify/vocab";
13
13
  import { encodeBase64 } from "byte-encodings/base64";
package/dist/sig/mod.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
- const require_http = require("../http-CsQRvInz.cjs");
5
- const require_proof = require("../proof-JfqNWc2E.cjs");
4
+ const require_http = require("../http-Cginz7L0.cjs");
5
+ const require_proof = require("../proof-BbHMAkPG.cjs");
6
6
  exports.attachSignature = require_proof.attachSignature;
7
7
  exports.createProof = require_proof.createProof;
8
8
  exports.createSignature = require_proof.createSignature;
package/dist/sig/mod.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { _ as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKey, d as importJwk, g as parseAcceptSignature, h as fulfillAcceptSignature, i as verifyRequest, l as fetchKeyDetailed, m as formatAcceptSignature, o as exportJwk, r as signRequest, u as generateCryptoKeyPair } from "../http-DQVY5EMT.js";
4
- import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "../proof-uEay4EIq.js";
3
+ import { _ as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKey, d as importJwk, g as parseAcceptSignature, h as fulfillAcceptSignature, i as verifyRequest, l as fetchKeyDetailed, m as formatAcceptSignature, o as exportJwk, r as signRequest, u as generateCryptoKeyPair } from "../http-B3rSb2GQ.js";
4
+ import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "../proof-cGfRPTiV.js";
5
5
  export { attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, fetchKeyDetailed, formatAcceptSignature, fulfillAcceptSignature, generateCryptoKeyPair, getKeyOwner, importJwk, parseAcceptSignature, signJsonLd, signObject, signRequest, validateAcceptSignature, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifyRequestDetailed, verifySignature };
@@ -2,11 +2,11 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
5
- import "../std__assert-BUOJXi-Z.mjs";
6
- import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
5
+ import "../std__assert-DflMZha5.mjs";
6
+ import { r as assertFalse } from "../assert_throws-D4DNWrux.mjs";
7
7
  import { t as assert } from "../assert-BhQd8QHP.mjs";
8
8
  import { o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-CtZLJq76.mjs";
9
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-DnOwKsK6.mjs";
9
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-C8IVASD3.mjs";
10
10
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
11
11
  import { Create, CryptographicKey, lookupObject } from "@fedify/vocab";
12
12
  //#region src/sig/owner.test.ts