@fedify/fedify 2.4.0-dev.2108 → 2.4.0-dev.2115

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 (92) hide show
  1. package/dist/{builder-BRlYEsrV.mjs → builder-CZcBWrBd.mjs} +3 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-CHGINiJg.mjs → compound-proof-CDuP9MIX.mjs} +3 -3
  6. package/dist/{context-Cqx9M_zO.d.cts → context-SMNOiiY6.d.cts} +1 -1
  7. package/dist/{context-B4F4kGJq.d.ts → context-tOiJfBoD.d.ts} +1 -1
  8. package/dist/{deno-CC4RKEEp.mjs → deno-CCABdw7r.mjs} +1 -1
  9. package/dist/{docloader-D3_m98fe.mjs → docloader-Bby5g2iu.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +5 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/keycache.test.mjs +67 -1
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +7 -7
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +2 -2
  21. package/dist/federation/mod.d.ts +2 -2
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/outgoing-proof.test.mjs +5 -5
  24. package/dist/federation/portable-actor.test.mjs +3 -3
  25. package/dist/federation/portable-gateway-keys.test.mjs +24 -7
  26. package/dist/federation/portable-inbox.test.mjs +4 -4
  27. package/dist/federation/portable.test.mjs +3 -3
  28. package/dist/federation/send.test.mjs +3 -3
  29. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  30. package/dist/federation/tasks/tasks.test.mjs +2 -2
  31. package/dist/federation/temporal.test.mjs +1 -1
  32. package/dist/federation/webfinger.test.mjs +1 -1
  33. package/dist/{federation-dqo6ZzgT.cjs → federation-DYr384xz.cjs} +71 -3
  34. package/dist/{http-B7wEqv49.cjs → http-B4mQvNcv.cjs} +140 -55
  35. package/dist/{http-CHEYA5fH.mjs → http-BcLnr2do.mjs} +6 -12
  36. package/dist/{http-xlG-jRdB.d.cts → http-BrQgkJay.d.ts} +12 -7
  37. package/dist/{http-CaUBCHYJ.js → http-Ce6_Lf7k.js} +140 -55
  38. package/dist/{http-n2v6DPxu.d.ts → http-D76Py0M4.d.cts} +12 -7
  39. package/dist/{key-B8u1vdvF.mjs → key-CkNxztNV.mjs} +107 -16
  40. package/dist/{keycache-BUDHB5rY.mjs → keycache-BInaECGj.mjs} +68 -0
  41. package/dist/{kv-cache-0pYMPrkL.cjs → kv-cache-Dqp_E-1g.cjs} +1 -1
  42. package/dist/{kv-cache-ibncHN1D.js → kv-cache-DwzXzchj.js} +1 -1
  43. package/dist/{kv-cache-CTdvf_Iq.mjs → kv-cache-iFaRIcIL.mjs} +1 -1
  44. package/dist/{ld-BTMJHEGV.mjs → ld-CnT0f1nU.mjs} +4 -7
  45. package/dist/{metrics-CF1TEvdS.mjs → metrics-em_7lwhG.mjs} +1 -1
  46. package/dist/{middleware-B-rZ7oE-.mjs → middleware-BpwKvOpU.mjs} +15 -15
  47. package/dist/{middleware-Dl-Y_ZlC.js → middleware-DhIBngkj.js} +71 -3
  48. package/dist/{middleware-DCMTJT3_.mjs → middleware-U9IuzvDO.mjs} +1 -1
  49. package/dist/{mod-4s_XgF17.d.ts → mod-COWgSo2v.d.ts} +1 -1
  50. package/dist/{mod-Coacostr.d.ts → mod-CTh7ijx5.d.ts} +1 -1
  51. package/dist/{mod-xGFCHqq_.d.cts → mod-Cr79cQlc.d.cts} +1 -1
  52. package/dist/{mod-CVZrXSdm.d.ts → mod-Cwdt_CWU.d.ts} +1 -1
  53. package/dist/{mod-C8LieBzF.d.cts → mod-DMdGFMEl.d.cts} +1 -1
  54. package/dist/{mod-BuFmStNT.d.cts → mod-DnElMslb.d.cts} +1 -1
  55. package/dist/mod.cjs +5 -5
  56. package/dist/mod.d.cts +5 -5
  57. package/dist/mod.d.ts +5 -5
  58. package/dist/mod.js +5 -5
  59. package/dist/nodeinfo/handler.test.mjs +1 -1
  60. package/dist/{owner-CY--JSqO.mjs → owner-9LiHmOv1.mjs} +4 -4
  61. package/dist/{owner-CGnHPl4k.cjs → owner-BtoMCv6Z.cjs} +1 -1
  62. package/dist/{owner-M8dOKEl6.js → owner-Cb3ytrlq.js} +1 -1
  63. package/dist/{portable-key-C0-ghkgM.mjs → portable-key-9ck61lI0.mjs} +5 -4
  64. package/dist/{portable-key-id-Bva5iT3W.mjs → portable-key-id-T7lecIbt.mjs} +3 -2
  65. package/dist/{proof-DDw0i9b1.mjs → proof-xdPYGPaA.mjs} +32 -31
  66. package/dist/{send-CU3sHGPt.mjs → send-Bf8rSWch.mjs} +3 -3
  67. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  68. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  69. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  70. package/dist/sig/compound-proof.test.mjs +2 -2
  71. package/dist/sig/http.test.mjs +2 -2
  72. package/dist/sig/key.test.mjs +1 -1
  73. package/dist/sig/ld.test.mjs +2 -2
  74. package/dist/sig/mod.cjs +3 -3
  75. package/dist/sig/mod.d.cts +2 -2
  76. package/dist/sig/mod.d.ts +2 -2
  77. package/dist/sig/mod.js +3 -3
  78. package/dist/sig/owner.test.mjs +1 -1
  79. package/dist/sig/portable-collection.test.mjs +2 -2
  80. package/dist/sig/portable-key.test.mjs +301 -23
  81. package/dist/sig/proof.test.mjs +3 -3
  82. package/dist/{sig-C2CORtvg.cjs → sig-Bj3fHnv0.cjs} +2 -2
  83. package/dist/{sig-fycq7sOr.js → sig-DN9pOglI.js} +2 -2
  84. package/dist/{temporal-NHiSpGcW.mjs → temporal-tAtwv6vX.mjs} +1 -1
  85. package/dist/testing/kv.test.mjs +1 -1
  86. package/dist/utils/docloader.test.mjs +2 -2
  87. package/dist/utils/kv-cache.test.mjs +1 -1
  88. package/dist/utils/mod.cjs +1 -1
  89. package/dist/utils/mod.d.cts +1 -1
  90. package/dist/utils/mod.d.ts +1 -1
  91. package/dist/utils/mod.js +1 -1
  92. package/package.json +6 -6
@@ -1,11 +1,15 @@
1
- import "temporal-polyfill";
1
+ import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { r as fetchKey } from "../key-B8u1vdvF.mjs";
5
- import { d as verifyRequest, l as signRequest } from "../http-CHEYA5fH.mjs";
6
- import { c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
7
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-CY--JSqO.mjs";
8
- import { Create, CryptographicKey, Multikey, Person } from "@fedify/vocab";
4
+ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
+ import { i as fetchKey } from "../key-CkNxztNV.mjs";
6
+ import { h as verifyJsonLd, p as signJsonLd } from "../ld-CnT0f1nU.mjs";
7
+ import { n as createProof, u as verifyProof } from "../proof-xdPYGPaA.mjs";
8
+ import { d as verifyRequest, f as verifyRequestDetailed, l as signRequest } from "../http-BcLnr2do.mjs";
9
+ import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
10
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-9LiHmOv1.mjs";
11
+ import { t as KvKeyCache } from "../keycache-BInaECGj.mjs";
12
+ import { Create, CryptographicKey, Multikey, Note, Person } from "@fedify/vocab";
9
13
  import { test } from "@fedify/fixture";
10
14
  import { FetchError, encodeMultibase, exportDidKey, exportSpki, parseIri, preloadedContexts } from "@fedify/vocab-runtime";
11
15
  import serialize from "json-canon";
@@ -46,14 +50,15 @@ function createLoader(responses) {
46
50
  };
47
51
  return Object.assign(loader, { fetched });
48
52
  }
49
- async function sign(document, privateKey = ed25519PrivateKey, verificationMethod = didKeyId) {
53
+ async function sign(document, privateKey = ed25519PrivateKey, verificationMethod = didKeyId, proofOptions = {}) {
50
54
  const proofConfig = {
51
55
  "@context": document["@context"],
52
56
  type: "DataIntegrityProof",
53
57
  cryptosuite: "eddsa-jcs-2022",
54
58
  verificationMethod,
55
59
  proofPurpose: "assertionMethod",
56
- created: "2023-02-24T23:36:38Z"
60
+ created: "2023-02-24T23:36:38Z",
61
+ ...proofOptions
57
62
  };
58
63
  const encoder = new TextEncoder();
59
64
  const proofDigest = await crypto.subtle.digest("SHA-256", encoder.encode(serialize(proofConfig)));
@@ -333,32 +338,305 @@ test("keys at ordinary URLs cannot belong to portable actors", async () => {
333
338
  keyCache
334
339
  })).key, null);
335
340
  });
336
- test("gateway keys are neither cached nor accepted outside HTTP Signatures", async () => {
341
+ const specs = ["draft-cavage-http-signatures-12", "rfc9421"];
342
+ function fetchKeyOf(cls, options) {
343
+ return cls === CryptographicKey ? fetchKey(keyId, CryptographicKey, options) : fetchKey(keyId, Multikey, options);
344
+ }
345
+ function fetchCount(loader, url = keyId) {
346
+ const document = url.href.replace(/#.*$/, "");
347
+ return loader.fetched.filter((u) => u.replace(/#.*$/, "") === document).length;
348
+ }
349
+ test("verifyRequest() caches failures to fetch compatible key IDs", async () => {
350
+ const unreachable = {
351
+ "404 Not Found": createLoader({}),
352
+ "a network error": (url) => Promise.reject(/* @__PURE__ */ new TypeError(`${url}`))
353
+ };
354
+ for (const spec of specs) for (const [name, inner] of Object.entries(unreachable)) {
355
+ const fetched = [];
356
+ const documentLoader = Object.assign((url) => {
357
+ fetched.push(url);
358
+ return inner(url);
359
+ }, { fetched });
360
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"]);
361
+ const options = {
362
+ documentLoader,
363
+ contextLoader,
364
+ keyCache,
365
+ spec
366
+ };
367
+ for (let i = 0; i < 3; i++) {
368
+ const result = await verifyRequestDetailed(await signedRequest(spec), options);
369
+ ok(!result.verified, `${spec}, ${name}`);
370
+ strictEqual(result.reason.type, "keyFetchError", `${spec}, ${name}`);
371
+ }
372
+ strictEqual(fetchCount(documentLoader), 1, `${spec}, ${name}`);
373
+ for (const cls of [CryptographicKey, Multikey]) {
374
+ const result = await fetchKeyOf(cls, options);
375
+ strictEqual(result.key, null);
376
+ ok(result.cached, `${spec}, ${name}, ${cls.name}`);
377
+ }
378
+ strictEqual(fetchCount(documentLoader), 1, `${spec}, ${name}`);
379
+ }
380
+ });
381
+ test("verifyRequest() caches gateway keys only for HTTP Signatures", async () => {
382
+ for (const spec of specs) {
383
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson()) });
384
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"]);
385
+ const options = {
386
+ documentLoader,
387
+ contextLoader,
388
+ keyCache,
389
+ spec
390
+ };
391
+ for (let i = 0; i < 3; i++) strictEqual((await verifyRequest(await signedRequest(spec), options))?.ownerId?.href, parseIri(actorId).href, spec);
392
+ strictEqual(fetchCount(documentLoader), 1, spec);
393
+ const document = {
394
+ "@context": "https://www.w3.org/ns/activitystreams",
395
+ type: "Create",
396
+ id: "https://gw1.example/activities/1",
397
+ actor: compatibleId(gw1)
398
+ };
399
+ const { signature } = await signJsonLd(document, rsaPrivateKey2, keyId, { contextLoader });
400
+ ok(!await verifyJsonLd({
401
+ ...document,
402
+ signature
403
+ }, options), spec);
404
+ const note = new Note({
405
+ id: new URL("https://gw1.example/notes/1"),
406
+ attribution: new URL(compatibleId(gw1)),
407
+ content: "Hello"
408
+ });
409
+ const proof = await createProof(note, ed25519PrivateKey, keyId, { contextLoader });
410
+ strictEqual(await verifyProof(await note.toJsonLd({
411
+ format: "compact",
412
+ contextLoader
413
+ }), proof, options), null, spec);
414
+ for (const cls of [CryptographicKey, Multikey]) strictEqual((await fetchKeyOf(cls, options)).key, null, spec);
415
+ const fetched = fetchCount(documentLoader);
416
+ ok(await verifyRequest(await signedRequest(spec), options) != null, spec);
417
+ strictEqual(fetchCount(documentLoader), fetched, spec);
418
+ }
419
+ });
420
+ test("verifyRequest() is not rejected by other purposes' negative entries", async () => {
421
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson()) });
422
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"]);
423
+ const options = {
424
+ documentLoader,
425
+ contextLoader,
426
+ keyCache
427
+ };
428
+ for (const cls of [CryptographicKey, Multikey]) strictEqual((await fetchKeyOf(cls, options)).key, null);
429
+ ok(await verifyRequest(await signedRequest(), options) != null);
430
+ const fetched = fetchCount(documentLoader);
431
+ for (const cls of [CryptographicKey, Multikey]) {
432
+ const result = await fetchKeyOf(cls, options);
433
+ strictEqual(result.key, null);
434
+ ok(result.cached);
435
+ }
436
+ strictEqual(fetchCount(documentLoader), fetched);
437
+ });
438
+ test("fetchKey() does not share negative entries of compatible key IDs across key classes", async () => {
439
+ const ownerId = new URL("https://gw1.example/users/bob");
440
+ const multikey = new Multikey({
441
+ id: keyId,
442
+ controller: ownerId,
443
+ publicKey: ed25519PublicKey.publicKey
444
+ });
445
+ const documentLoader = createLoader({
446
+ [compatibleId(gw1)]: await multikey.toJsonLd({ contextLoader }),
447
+ [ownerId.href]: await new Person({
448
+ id: ownerId,
449
+ assertionMethods: [multikey]
450
+ }).toJsonLd({ contextLoader })
451
+ });
452
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"]);
453
+ const options = {
454
+ documentLoader,
455
+ contextLoader,
456
+ keyCache
457
+ };
458
+ strictEqual((await fetchKey(keyId, CryptographicKey, options)).key, null);
459
+ strictEqual(await verifyRequest(await signedRequest(), options), null);
460
+ const result = await fetchKey(keyId, Multikey, options);
461
+ strictEqual(result.key?.id?.href, keyId.href);
462
+ ok(!result.cached);
463
+ const cached = await fetchKey(keyId, Multikey, options);
464
+ strictEqual(cached.key?.id?.href, keyId.href);
465
+ ok(cached.cached);
466
+ const other = await fetchKey(keyId, CryptographicKey, options);
467
+ strictEqual(other.key, null);
468
+ ok(other.cached);
469
+ });
470
+ test("verifyRequest() caches rejected portable actor documents", async () => {
471
+ for (const spec of specs) {
472
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await actorJson() });
473
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"]);
474
+ const options = {
475
+ documentLoader,
476
+ contextLoader,
477
+ keyCache,
478
+ spec
479
+ };
480
+ for (let i = 0; i < 3; i++) {
481
+ const result = await verifyRequestDetailed(await signedRequest(spec), options);
482
+ ok(!result.verified, spec);
483
+ strictEqual(result.reason.type, "invalidSignature", spec);
484
+ }
485
+ strictEqual(fetchCount(documentLoader), 1, spec);
486
+ }
487
+ });
488
+ test("verifyRequest() refetches a cached gateway key that fails to verify", async () => {
489
+ for (const spec of specs) {
490
+ const responses = { [compatibleId(gw1)]: await sign(await actorJson()) };
491
+ const documentLoader = createLoader(responses);
492
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"]);
493
+ const options = {
494
+ documentLoader,
495
+ contextLoader,
496
+ keyCache,
497
+ spec
498
+ };
499
+ ok(await verifyRequest(await signedRequest(spec), options) != null);
500
+ responses[compatibleId(gw1)] = await sign(await actorJson({
501
+ publicKey: rsaPublicKey3.publicKey,
502
+ assertionMethod: false
503
+ }));
504
+ const rotated = async () => await signRequest(new Request("https://recipient.example/inbox", {
505
+ method: "POST",
506
+ body: "{}",
507
+ headers: { "Content-Type": "application/activity+json" }
508
+ }), rsaPrivateKey3, keyId, { spec });
509
+ ok(await verifyRequest(await rotated(), options) != null, spec);
510
+ strictEqual(fetchCount(documentLoader), 2, spec);
511
+ ok(await verifyRequest(await rotated(), options) != null, spec);
512
+ strictEqual(fetchCount(documentLoader), 2, spec);
513
+ }
514
+ });
515
+ test("verifyRequest() drops a cached gateway key when its refetch fails", async () => {
516
+ for (const spec of specs) {
517
+ const responses = { [compatibleId(gw1)]: await sign(await actorJson()) };
518
+ const documentLoader = createLoader(responses);
519
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"]);
520
+ const options = {
521
+ documentLoader,
522
+ contextLoader,
523
+ keyCache,
524
+ spec
525
+ };
526
+ ok(await verifyRequest(await signedRequest(spec), options) != null);
527
+ delete responses[compatibleId(gw1)];
528
+ const result = await verifyRequestDetailed(await signRequest(new Request("https://recipient.example/inbox", {
529
+ method: "POST",
530
+ body: "{}",
531
+ headers: { "Content-Type": "application/activity+json" }
532
+ }), rsaPrivateKey3, keyId, { spec }), options);
533
+ ok(!result.verified, spec);
534
+ strictEqual(result.reason.type, "keyFetchError", spec);
535
+ strictEqual(fetchCount(documentLoader), 2, spec);
536
+ const next = await verifyRequestDetailed(await signedRequest(spec), options);
537
+ ok(!next.verified, spec);
538
+ strictEqual(next.reason.type, "keyFetchError", spec);
539
+ strictEqual(fetchCount(documentLoader), 2, spec);
540
+ }
541
+ });
542
+ test("verifyRequest() does not trust a cached gateway key for longer than its proof", async () => {
543
+ let now = Temporal.Now.instant();
544
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"], { now: () => now });
545
+ const expires = now.add({ minutes: 30 });
546
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson(), ed25519PrivateKey, didKeyId, { expires: expires.toString() }) });
547
+ const options = {
548
+ documentLoader,
549
+ contextLoader,
550
+ keyCache
551
+ };
552
+ ok(await verifyRequest(await signedRequest(), options) != null);
553
+ now = expires.subtract({ seconds: 1 });
554
+ ok(await verifyRequest(await signedRequest(), options) != null);
555
+ strictEqual(fetchCount(documentLoader), 1);
556
+ now = expires;
557
+ ok(await verifyRequest(await signedRequest(), options) != null);
558
+ strictEqual(fetchCount(documentLoader), 2);
559
+ });
560
+ test("verifyRequest() looks up a cached gateway key again after an hour", async () => {
561
+ let now = Temporal.Now.instant();
562
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"], { now: () => now });
563
+ const responses = { [compatibleId(gw1)]: await sign(await actorJson()) };
564
+ const documentLoader = createLoader(responses);
565
+ const options = {
566
+ documentLoader,
567
+ contextLoader,
568
+ keyCache
569
+ };
570
+ const start = now;
571
+ ok(await verifyRequest(await signedRequest(), options) != null);
572
+ now = start.add({ minutes: 59 });
573
+ ok(await verifyRequest(await signedRequest(), options) != null);
574
+ strictEqual(fetchCount(documentLoader), 1);
575
+ responses[compatibleId(gw1)] = await sign(await actorJson({ gateways: [gw2] }));
576
+ now = start.add({ hours: 1 });
577
+ strictEqual(await verifyRequest(await signedRequest(), options), null);
578
+ strictEqual(fetchCount(documentLoader), 2);
579
+ });
580
+ test("verifyRequest() rejects and caches gateway keys of expired documents", async () => {
581
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson(), ed25519PrivateKey, didKeyId, { expires: "2000-01-01T00:00:00Z" }) });
582
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"]);
583
+ const options = {
584
+ documentLoader,
585
+ contextLoader,
586
+ keyCache
587
+ };
588
+ strictEqual(await verifyRequest(await signedRequest(), options), null);
589
+ strictEqual(await verifyRequest(await signedRequest(), options), null);
590
+ strictEqual(fetchCount(documentLoader), 1);
591
+ });
592
+ test("verifyRequest() takes the expiration of a proof whatever its context", async () => {
593
+ let now = Temporal.Now.instant();
594
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"], { now: () => now });
595
+ const expires = now.add({ minutes: 30 });
596
+ const actor = await actorJson();
597
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign({
598
+ ...actor,
599
+ "@context": [...actor["@context"], { expires: "https://example.com/ns#notExpiration" }]
600
+ }, ed25519PrivateKey, didKeyId, { expires: expires.toString() }) });
601
+ const options = {
602
+ documentLoader,
603
+ contextLoader,
604
+ keyCache
605
+ };
606
+ ok(await verifyRequest(await signedRequest(), options) != null);
607
+ now = expires.subtract({ seconds: 1 });
608
+ ok(await verifyRequest(await signedRequest(), options) != null);
609
+ strictEqual(fetchCount(documentLoader), 1);
610
+ now = expires;
611
+ ok(await verifyRequest(await signedRequest(), options) != null);
612
+ strictEqual(fetchCount(documentLoader), 2);
613
+ });
614
+ test("a key cache without namespaces caches only fetch failures of compatible key IDs", async () => {
337
615
  const calls = [];
616
+ const entries = /* @__PURE__ */ new Map();
338
617
  const keyCache = {
339
618
  get(id) {
340
619
  calls.push(`get ${id.href}`);
341
- return Promise.resolve(void 0);
620
+ return Promise.resolve(entries.get(id.href));
342
621
  },
343
- set(id) {
344
- calls.push(`set ${id.href}`);
622
+ set(id, key) {
623
+ calls.push(`set ${id.href} ${key == null ? "null" : "key"}`);
624
+ entries.set(id.href, key);
345
625
  return Promise.resolve();
346
626
  }
347
627
  };
348
- const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson()) });
349
- ok(await verifyRequest(await signedRequest(), {
350
- documentLoader,
351
- contextLoader,
352
- keyCache
353
- }) != null);
354
- const fetchOptions = {
355
- documentLoader,
628
+ const options = {
629
+ documentLoader: createLoader({ [compatibleId(gw1)]: await sign(await actorJson()) }),
356
630
  contextLoader,
357
631
  keyCache
358
632
  };
359
- strictEqual((await fetchKey(keyId, CryptographicKey, fetchOptions)).key, null);
360
- strictEqual((await fetchKey(keyId, Multikey, fetchOptions)).key, null);
361
- deepStrictEqual(calls.filter((c) => c.includes(keyId.origin)), []);
633
+ ok(await verifyRequest(await signedRequest(), options) != null);
634
+ for (const cls of [CryptographicKey, Multikey]) strictEqual((await fetchKeyOf(cls, options)).key, null);
635
+ ok(await verifyRequest(await signedRequest(), options) != null);
636
+ deepStrictEqual(calls.filter((c) => c.startsWith("set ") && c.includes(keyId.origin)), []);
637
+ const unreachable = new URL(`${compatibleId(gw2)}#main-key`);
638
+ strictEqual((await fetchKey(unreachable, CryptographicKey, options)).key, null);
639
+ deepStrictEqual(calls.filter((c) => c.startsWith("set ") && c.includes(gw2)), [`set ${unreachable.href} null`]);
362
640
  });
363
641
  test("doesActorOwnKey() and getKeyOwner() resolve gateway keys to portable actors", async () => {
364
642
  const options = {
@@ -10,10 +10,10 @@ import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
11
11
  import { t as assert } from "../assert-D1Eafmd_.mjs";
12
12
  import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
13
- import { i as signObject, n as createProof, o as verifyObject, r as hasProofLike, s as verifyPortableObjectProof, u as verifyProof } from "../proof-DDw0i9b1.mjs";
14
- import { d as verifyRequest } from "../http-CHEYA5fH.mjs";
13
+ import { i as signObject, n as createProof, o as verifyObject, r as hasProofLike, s as verifyPortableObjectProof, u as verifyProof } from "../proof-xdPYGPaA.mjs";
14
+ import { d as verifyRequest } from "../http-BcLnr2do.mjs";
15
15
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
16
- import { t as getAuthenticatedDocumentLoader } from "../docloader-D3_m98fe.mjs";
16
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-Bby5g2iu.mjs";
17
17
  import { Create, DataIntegrityProof, Document, Multikey, Note, PUBLIC_COLLECTION, Place, Translation } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { decodeMultibase, encodeMultibase, exportDidKey, exportMultibaseKey, formatIri, importMultibaseKey, parseIri } from "@fedify/vocab-runtime";
@@ -1,6 +1,6 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-B7wEqv49.cjs");
3
- require("./owner-CGnHPl4k.cjs");
2
+ const require_http = require("./http-B4mQvNcv.cjs");
3
+ require("./owner-BtoMCv6Z.cjs");
4
4
  let _logtape_logtape = require("@logtape/logtape");
5
5
  let _fedify_vocab = require("@fedify/vocab");
6
6
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { _ as verifyPortableObjectProofWithRoot, bt as name, h as verifyPortableObjectProof, xt as version } from "./http-CaUBCHYJ.js";
4
- import "./owner-M8dOKEl6.js";
3
+ import { _ as verifyPortableObjectProofWithRoot, bt as name, h as verifyPortableObjectProof, xt as version } from "./http-Ce6_Lf7k.js";
4
+ import "./owner-Cb3ytrlq.js";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { CollectionPage, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as getNormalizationContextLoader } from "./ld-BTMJHEGV.mjs";
4
+ import { c as getNormalizationContextLoader } from "./ld-CnT0f1nU.mjs";
5
5
  import jsonld from "@fedify/vocab-runtime/jsonld";
6
6
  //#region src/federation/temporal.ts
7
7
  function isPlainObject(value) {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
7
  import { n as baseOptions, o as stringSchema, t as MockQueue } from "../tasks-dbCShlCb.mjs";
8
8
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
9
- import { o as createFederation } from "../middleware-B-rZ7oE-.mjs";
9
+ import { o as createFederation } from "../middleware-BpwKvOpU.mjs";
10
10
  //#region src/testing/kv.test.ts
11
11
  test("ManualClockKvStore keeps an entry at its exact expiration instant", async () => {
12
12
  const kv = new ManualClockKvStore();
@@ -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 { d as verifyRequest } from "../http-CHEYA5fH.mjs";
8
+ import { d as verifyRequest } from "../http-BcLnr2do.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-DIJNHW-Y.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-D3_m98fe.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-Bby5g2iu.mjs";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
13
13
  //#region src/utils/docloader.test.ts
@@ -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-CTdvf_Iq.mjs";
4
+ import { n as kvCache, t as MockKvStore } from "../kv-cache-iFaRIcIL.mjs";
5
5
  import { deepStrictEqual, throws } from "node:assert";
6
6
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
7
7
  import { preloadedContexts } from "@fedify/vocab-runtime";
@@ -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-0pYMPrkL.cjs");
3
+ const require_kv_cache = require("../kv-cache-Dqp_E-1g.cjs");
4
4
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
5
5
  exports.kvCache = require_kv_cache.kvCache;
@@ -1,3 +1,3 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-xGFCHqq_.cjs";
2
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-Cr79cQlc.cjs";
3
3
  export { getAuthenticatedDocumentLoader, kvCache };
@@ -1,3 +1,3 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-CVZrXSdm.js";
2
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-Cwdt_CWU.js";
3
3
  export { getAuthenticatedDocumentLoader, 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-ibncHN1D.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-DwzXzchj.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "2.4.0-dev.2108+159ea471",
3
+ "version": "2.4.0-dev.2115+e2feaf2b",
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.4.0-dev.2108+159ea471",
155
- "@fedify/vocab-runtime": "2.4.0-dev.2108+159ea471",
156
- "@fedify/vocab": "2.4.0-dev.2108+159ea471",
157
- "@fedify/webfinger": "2.4.0-dev.2108+159ea471"
154
+ "@fedify/uri-template": "2.4.0-dev.2115+e2feaf2b",
155
+ "@fedify/vocab": "2.4.0-dev.2115+e2feaf2b",
156
+ "@fedify/vocab-runtime": "2.4.0-dev.2115+e2feaf2b",
157
+ "@fedify/webfinger": "2.4.0-dev.2115+e2feaf2b"
158
158
  },
159
159
  "devDependencies": {
160
160
  "@std/assert": "npm:@jsr/std__assert@^0.226.0",
@@ -167,7 +167,7 @@
167
167
  "typescript": "^6.0.0",
168
168
  "wrangler": "^4.17.0",
169
169
  "@fedify/fixture": "2.0.0",
170
- "@fedify/vocab-tools": "^2.4.0-dev.2108+159ea471"
170
+ "@fedify/vocab-tools": "^2.4.0-dev.2115+e2feaf2b"
171
171
  },
172
172
  "scripts": {
173
173
  "build:self": "tsdown",