@fedify/fedify 2.3.12 → 2.3.14

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 (69) hide show
  1. package/dist/{assert_rejects-DN60FHPX.mjs → assert_rejects-D1HYAik3.mjs} +2 -20
  2. package/dist/{assert_throws-BOkhLGYc.mjs → assert_throws-BbSMEFKo.mjs} +19 -1
  3. package/dist/{builder-B6ixdT3y.mjs → builder-C6yXExXl.mjs} +2 -2
  4. package/dist/compat/transformers.test.mjs +2 -2
  5. package/dist/{deno-D4dcoVfF.mjs → deno-y3L4Oa94.mjs} +1 -1
  6. package/dist/{docloader-NOKfswJV.mjs → docloader-yuyzZC9a.mjs} +6 -2
  7. package/dist/federation/builder.test.mjs +3 -3
  8. package/dist/federation/circuit-breaker.test.mjs +3 -3
  9. package/dist/federation/collection.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +262 -9
  11. package/dist/federation/idempotency.test.mjs +3 -3
  12. package/dist/federation/inbox.test.mjs +25 -1
  13. package/dist/federation/keycache.test.mjs +19 -8
  14. package/dist/federation/kv.test.mjs +1 -1
  15. package/dist/federation/metrics.test.mjs +3 -3
  16. package/dist/federation/middleware.test.mjs +177 -15
  17. package/dist/federation/mod.cjs +1 -1
  18. package/dist/federation/mod.js +1 -1
  19. package/dist/federation/mq.test.mjs +4 -3
  20. package/dist/federation/negotiation.test.mjs +4 -4
  21. package/dist/federation/retry.test.mjs +1 -1
  22. package/dist/federation/router.test.mjs +3 -4
  23. package/dist/federation/send.test.mjs +7 -6
  24. package/dist/federation/temporal.test.mjs +4 -4
  25. package/dist/federation/webfinger.test.mjs +2 -2
  26. package/dist/{http-HjidyDfF.mjs → http-BCSf1KIY.mjs} +3 -3
  27. package/dist/{http-D_Xrr6kp.cjs → http-D77Wjrcg.cjs} +17 -3
  28. package/dist/{http-DkyUBs_T.js → http-DNM9KyIO.js} +17 -3
  29. package/dist/inbox-NObX26c9.mjs +180 -0
  30. package/dist/{key-Dg-NXm4K.mjs → key-APURZ0ky.mjs} +18 -4
  31. package/dist/{keycache-Dkc_sflV.mjs → keycache-BimEQucx.mjs} +1 -1
  32. package/dist/{kv-cache-BwnC6JGt.cjs → kv-cache-Bg4utAuv.cjs} +5 -1
  33. package/dist/{kv-cache-BpVwvcSI.mjs → kv-cache-C69dzgQi.mjs} +1 -1
  34. package/dist/{kv-cache-BhGeZk_o.js → kv-cache-LBCwHNLu.js} +5 -1
  35. package/dist/{ld-C1wveXXf.mjs → ld-BoLqbDaL.mjs} +3 -3
  36. package/dist/{metrics-cIaMxG0v.mjs → metrics-DBSl8wEr.mjs} +1 -1
  37. package/dist/{middleware-DhOFTXmo.cjs → middleware-BQUQ0pPl.cjs} +107 -35
  38. package/dist/{middleware-CnrzVu9i.js → middleware-CRd1JmuL.js} +107 -35
  39. package/dist/{middleware-I-XlrRVl.mjs → middleware-CZ8tI1qL.mjs} +97 -195
  40. package/dist/{middleware-VxPpU0h_.mjs → middleware-DAqo2eOY.mjs} +1 -1
  41. package/dist/mod.cjs +4 -4
  42. package/dist/mod.js +4 -4
  43. package/dist/nodeinfo/client.test.mjs +2 -2
  44. package/dist/nodeinfo/handler.test.mjs +2 -2
  45. package/dist/nodeinfo/types.test.mjs +2 -2
  46. package/dist/otel/exporter.test.mjs +1 -1
  47. package/dist/{owner-CFKfE85G.mjs → owner-DjKvngeb.mjs} +2 -2
  48. package/dist/{proof-DBxu6g2K.cjs → proof-CWTnYhjS.cjs} +1 -1
  49. package/dist/{proof-DtpGEj0u.mjs → proof-D2oFOdNA.mjs} +3 -3
  50. package/dist/{proof-HU__qtUD.js → proof-s9sy44kx.js} +1 -1
  51. package/dist/{send-CwMea5r6.mjs → send-xDK5UnL-.mjs} +3 -3
  52. package/dist/sig/http.test.mjs +7 -7
  53. package/dist/sig/key.test.mjs +200 -6
  54. package/dist/sig/ld.test.mjs +50 -5
  55. package/dist/sig/mod.cjs +2 -2
  56. package/dist/sig/mod.js +2 -2
  57. package/dist/sig/owner.test.mjs +49 -4
  58. package/dist/sig/proof.test.mjs +56 -4
  59. package/dist/{std__assert-BvGuO4xO.mjs → std__assert-BjJn2daJ.mjs} +2 -2
  60. package/dist/{temporal-ClSXyHyu.mjs → temporal-C6icc5n9.mjs} +1 -1
  61. package/dist/utils/docloader.test.mjs +178 -39
  62. package/dist/utils/kv-cache.test.mjs +1 -1
  63. package/dist/utils/mod.cjs +1 -1
  64. package/dist/utils/mod.js +1 -1
  65. package/package.json +6 -6
  66. /package/dist/{assert-jSdZMYam.mjs → assert-CEO06XCw.mjs} +0 -0
  67. /package/dist/{circuit-breaker-test-utils-Dm5MM6l9.mjs → circuit-breaker-test-utils-CAdwjgMi.mjs} +0 -0
  68. /package/dist/{negotiation-CDW-_gUU.mjs → negotiation-DDstyBvc.mjs} +0 -0
  69. /package/dist/{retry-_VvV0h9f.mjs → retry-CXg_MBI-.mjs} +0 -0
@@ -3,11 +3,11 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-vrlUxr60.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
- import "../std__assert-BvGuO4xO.mjs";
7
- import { t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
- import { l as verifyRequest } from "../http-HjidyDfF.mjs";
6
+ import "../std__assert-BjJn2daJ.mjs";
7
+ import { t as assertRejects } from "../assert_rejects-D1HYAik3.mjs";
8
+ import { l as verifyRequest } from "../http-BCSf1KIY.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-BAK-tUlf.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-NOKfswJV.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-yuyzZC9a.mjs";
11
11
  import { configure, reset } from "@logtape/logtape";
12
12
  import { mockDocumentLoader, test } from "@fedify/fixture";
13
13
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -99,45 +99,50 @@ test("getAuthenticatedDocumentLoader() validates redirects", async (t) => {
99
99
  });
100
100
  esm_default.hardReset();
101
101
  });
102
- test("getAuthenticatedDocumentLoader() cancellation", {
103
- sanitizeResources: false,
104
- sanitizeOps: false
105
- }, async (t) => {
102
+ test("getAuthenticatedDocumentLoader() cancellation", async (t) => {
106
103
  esm_default.spyGlobal();
107
- await t.step("document loader cancellation", async () => {
108
- esm_default.get("https://example.com/slow-object", () => new Promise((resolve) => {
109
- setTimeout(() => {
110
- resolve({
111
- status: 200,
112
- headers: { "Content-Type": "application/activity+json" },
113
- body: {
114
- "@context": "https://www.w3.org/ns/activitystreams",
115
- type: "Note",
116
- content: "Slow response"
117
- }
104
+ try {
105
+ await t.step("document loader cancellation", async () => {
106
+ let responseTimer;
107
+ try {
108
+ esm_default.get("https://example.com/slow-object", () => new Promise((resolve) => {
109
+ responseTimer = setTimeout(() => {
110
+ resolve({
111
+ status: 200,
112
+ headers: { "Content-Type": "application/activity+json" },
113
+ body: {
114
+ "@context": "https://www.w3.org/ns/activitystreams",
115
+ type: "Note",
116
+ content: "Slow response"
117
+ }
118
+ });
119
+ }, 1e3);
120
+ }));
121
+ const loader = getAuthenticatedDocumentLoader({
122
+ keyId: new URL("https://example.com/key2"),
123
+ privateKey: rsaPrivateKey2
118
124
  });
119
- }, 1e3);
120
- }));
121
- const loader = getAuthenticatedDocumentLoader({
122
- keyId: new URL("https://example.com/key2"),
123
- privateKey: rsaPrivateKey2
125
+ const controller = new AbortController();
126
+ const promise = loader("https://example.com/slow-object", { signal: controller.signal });
127
+ controller.abort();
128
+ await assertRejects(() => promise, Error);
129
+ await assertRejects(() => loader("https://example.com/object", { signal: controller.signal }), Error);
130
+ } finally {
131
+ clearTimeout(responseTimer);
132
+ }
124
133
  });
125
- const controller = new AbortController();
126
- const promise = loader("https://example.com/slow-object", { signal: controller.signal });
127
- controller.abort();
128
- await assertRejects(() => promise, Error);
129
- await assertRejects(() => loader("https://example.com/object", { signal: controller.signal }), Error);
130
- });
131
- await t.step("immediate cancellation", async () => {
132
- const loader = getAuthenticatedDocumentLoader({
133
- keyId: new URL("https://example.com/key2"),
134
- privateKey: rsaPrivateKey2
134
+ await t.step("immediate cancellation", async () => {
135
+ const loader = getAuthenticatedDocumentLoader({
136
+ keyId: new URL("https://example.com/key2"),
137
+ privateKey: rsaPrivateKey2
138
+ });
139
+ const controller = new AbortController();
140
+ controller.abort();
141
+ await assertRejects(() => loader("https://example.com/object", { signal: controller.signal }), Error);
135
142
  });
136
- const controller = new AbortController();
137
- controller.abort();
138
- await assertRejects(() => loader("https://example.com/object", { signal: controller.signal }), Error);
139
- });
140
- esm_default.hardReset();
143
+ } finally {
144
+ esm_default.hardReset();
145
+ }
141
146
  });
142
147
  test("getAuthenticatedDocumentLoader() bounds JSON after redirects", async () => {
143
148
  esm_default.mockGlobal();
@@ -456,5 +461,139 @@ test("getAuthenticatedDocumentLoader() rejects cancellation before fetching", as
456
461
  esm_default.hardReset();
457
462
  }
458
463
  });
464
+ const unsupportedUrls = [
465
+ "ftp://example.com/collection",
466
+ "file:///collection",
467
+ `data:application/ld+json,${encodeURIComponent(JSON.stringify({ id: "https://example.com/data" }))}`
468
+ ];
469
+ function isCredentialUrlError(error) {
470
+ return error instanceof UrlError && error.reason === "disallowed" && error.message === "URLs with credentials are not allowed";
471
+ }
472
+ test("getAuthenticatedDocumentLoader() rejects URLs with credentials", async (t) => {
473
+ const identity = {
474
+ keyId: new URL("https://example.com/key2"),
475
+ privateKey: rsaPrivateKey2
476
+ };
477
+ const start = "https://8.8.8.8/start";
478
+ const target = "https://user:pass@8.8.8.8/document";
479
+ for (const allowPrivateAddress of [false, true]) {
480
+ const loader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress });
481
+ for (const path of [
482
+ "direct",
483
+ "redirect",
484
+ "Link",
485
+ "HTML"
486
+ ]) await t.step(`${path}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
487
+ await withFetchStub((request) => {
488
+ if (request.url !== start) return Response.json({});
489
+ if (path === "redirect") return Response.redirect(target, 302);
490
+ if (path === "HTML") return new Response(`<link rel="alternate" type="application/activity+json" href="${target}">`, { headers: { "Content-Type": "text/html" } });
491
+ return new Response("", { headers: {
492
+ "Content-Type": "text/plain",
493
+ Link: `<${target}>; rel="alternate"; type="application/activity+json"`
494
+ } });
495
+ }, async (fetched) => {
496
+ await rejects(loader(path === "direct" ? target : start), isCredentialUrlError);
497
+ deepStrictEqual(fetched.map((r) => r.url), path === "direct" ? [] : [start]);
498
+ });
499
+ });
500
+ for (const url of [
501
+ "https://user@8.8.8.8/document",
502
+ "https://:pass@8.8.8.8/document",
503
+ "https://%75ser:p%40ss@8.8.8.8/document",
504
+ "http://user:pass@8.8.8.8/document"
505
+ ]) await t.step(`${url}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
506
+ await withFetchStub(() => Response.json({}), async (fetched) => {
507
+ await rejects(loader(url), isCredentialUrlError);
508
+ deepStrictEqual(fetched, []);
509
+ });
510
+ });
511
+ for (const url of ["https://:@8.8.8.8/document", "https://8.8.8.8/@user?q=@pass"]) await t.step(`allows ${url}, allowPrivateAddress=${allowPrivateAddress}`, async () => {
512
+ await withFetchStub(() => Response.json({ done: true }), async (fetched) => {
513
+ deepStrictEqual((await loader(url)).document, { done: true });
514
+ deepStrictEqual(fetched.map((r) => r.url), [new URL(url).href]);
515
+ });
516
+ });
517
+ }
518
+ for (const firstSpec of ["rfc9421", "draft-cavage-http-signatures-12"]) await t.step(`redirect after fallback from ${firstSpec}`, async () => {
519
+ const loader = getAuthenticatedDocumentLoader(identity, {
520
+ allowPrivateAddress: true,
521
+ specDeterminer: {
522
+ determineSpec: () => firstSpec,
523
+ rememberSpec: () => {}
524
+ }
525
+ });
526
+ await withFetchStub((request) => request.url !== start ? Response.json({}) : request.headers.has("Signature-Input") === (firstSpec === "rfc9421") ? new Response(null, { status: 401 }) : Response.redirect(target, 302), async (fetched) => {
527
+ await rejects(loader(start), isCredentialUrlError);
528
+ deepStrictEqual(fetched.map((r) => r.url), [start, start]);
529
+ deepStrictEqual(fetched.map((r) => r.headers.has("Signature-Input")), [firstSpec === "rfc9421", firstSpec !== "rfc9421"]);
530
+ });
531
+ });
532
+ });
533
+ function isUnsupportedProtocolError(error, url) {
534
+ return error instanceof UrlError && error.message === `Unsupported protocol: ${new URL(url).protocol}` && error.reason === "disallowed";
535
+ }
536
+ async function withFetchStub(respond, callback) {
537
+ const fetched = [];
538
+ const originalFetch = globalThis.fetch;
539
+ globalThis.fetch = ((input, init) => {
540
+ const request = input instanceof Request && init == null ? input : new Request(input, init);
541
+ fetched.push(request);
542
+ return Promise.resolve(respond(request));
543
+ });
544
+ try {
545
+ await callback(fetched);
546
+ } finally {
547
+ globalThis.fetch = originalFetch;
548
+ }
549
+ }
550
+ for (const url of unsupportedUrls) {
551
+ const scheme = new URL(url).protocol;
552
+ const loader = getAuthenticatedDocumentLoader({
553
+ keyId: new URL("https://example.com/key2"),
554
+ privateKey: rsaPrivateKey2
555
+ }, { allowPrivateAddress: true });
556
+ test(`getAuthenticatedDocumentLoader() rejects ${scheme} URLs with private addresses allowed before fetching`, async () => {
557
+ await withFetchStub(() => Response.json({}), async (fetched) => {
558
+ await rejects(loader(url), (e) => isUnsupportedProtocolError(e, url));
559
+ deepStrictEqual(fetched, []);
560
+ });
561
+ });
562
+ test(`getAuthenticatedDocumentLoader() rejects redirects to ${scheme} URLs with private addresses allowed`, async () => {
563
+ const start = "https://example.com/redirect-to-unsupported";
564
+ await withFetchStub(() => Response.redirect(url, 302), async (fetched) => {
565
+ await rejects(loader(start), (e) => isUnsupportedProtocolError(e, url));
566
+ deepStrictEqual(fetched.map((r) => r.url), [start]);
567
+ });
568
+ });
569
+ for (const firstSpec of ["rfc9421", "draft-cavage-http-signatures-12"]) test(`getAuthenticatedDocumentLoader() rejects redirects to ${scheme} URLs after signature fallback from ${firstSpec}`, async () => {
570
+ const start = "https://example.com/fallback-redirect-to-unsupported";
571
+ const fallbackLoader = getAuthenticatedDocumentLoader({
572
+ keyId: new URL("https://example.com/key2"),
573
+ privateKey: rsaPrivateKey2
574
+ }, {
575
+ allowPrivateAddress: true,
576
+ specDeterminer: {
577
+ determineSpec: () => firstSpec,
578
+ rememberSpec: () => {}
579
+ }
580
+ });
581
+ await withFetchStub((request) => request.headers.has("Signature-Input") === (firstSpec === "rfc9421") ? new Response("Unauthorized", { status: 401 }) : Response.redirect(url, 302), async (fetched) => {
582
+ await rejects(fallbackLoader(start), (e) => isUnsupportedProtocolError(e, url));
583
+ deepStrictEqual(fetched.map((r) => r.url), [start, start]);
584
+ deepStrictEqual(fetched.map((r) => r.headers.has("Signature-Input")), [firstSpec === "rfc9421", firstSpec !== "rfc9421"]);
585
+ });
586
+ });
587
+ for (const html of [false, true]) test(`getAuthenticatedDocumentLoader() rejects ${html ? "HTML" : "Link header"} alternates to ${scheme} URLs with private addresses allowed`, async () => {
588
+ const start = "https://example.com/alternate-to-unsupported";
589
+ await withFetchStub(() => html ? new Response(`<link rel="alternate" type="application/activity+json" href="${url}">`, { headers: { "Content-Type": "text/html" } }) : new Response("", { headers: {
590
+ "Content-Type": "text/plain",
591
+ Link: `<${url}>; rel="alternate"; type="application/activity+json"`
592
+ } }), async (fetched) => {
593
+ await rejects(loader(start), (e) => isUnsupportedProtocolError(e, url));
594
+ deepStrictEqual(fetched.map((r) => r.url), [start]);
595
+ });
596
+ });
597
+ }
459
598
  //#endregion
460
599
  export {};
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as kvCache, t as MockKvStore } from "../kv-cache-BpVwvcSI.mjs";
4
+ import { n as kvCache, t as MockKvStore } from "../kv-cache-C69dzgQi.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,6 +1,6 @@
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_kv_cache = require("../kv-cache-BwnC6JGt.cjs");
4
+ const require_kv_cache = require("../kv-cache-Bg4utAuv.cjs");
5
5
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
6
6
  exports.kvCache = require_kv_cache.kvCache;
package/dist/utils/mod.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-BhGeZk_o.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-LBCwHNLu.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "2.3.12",
3
+ "version": "2.3.14",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -152,10 +152,10 @@
152
152
  "jsonld": "^9.0.0",
153
153
  "structured-field-values": "^2.0.4",
154
154
  "urlpattern-polyfill": "^10.1.0",
155
- "@fedify/uri-template": "2.3.12",
156
- "@fedify/vocab": "2.3.12",
157
- "@fedify/webfinger": "2.3.12",
158
- "@fedify/vocab-runtime": "2.3.12"
155
+ "@fedify/vocab-runtime": "2.3.14",
156
+ "@fedify/uri-template": "2.3.14",
157
+ "@fedify/webfinger": "2.3.14",
158
+ "@fedify/vocab": "2.3.14"
159
159
  },
160
160
  "devDependencies": {
161
161
  "@std/assert": "npm:@jsr/std__assert@^0.226.0",
@@ -168,7 +168,7 @@
168
168
  "typescript": "^6.0.0",
169
169
  "wrangler": "^4.17.0",
170
170
  "@fedify/fixture": "2.0.0",
171
- "@fedify/vocab-tools": "^2.3.12"
171
+ "@fedify/vocab-tools": "^2.3.14"
172
172
  },
173
173
  "scripts": {
174
174
  "build:self": "tsdown",
File without changes