@fedify/fedify 2.4.0-dev.1962 → 2.4.0-dev.1988
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.
- package/dist/{builder-CWYQXC2W.mjs → builder-Cts78G9N.mjs} +2 -2
- package/dist/{client-CtxvmazV.mjs → client-DpnpgjyX.mjs} +78 -4
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{context-COLwHogJ.d.cts → context-BeDnDuF3.d.cts} +4 -3
- package/dist/{context-DiiCt4S7.d.ts → context-uyRUWoZQ.d.ts} +4 -3
- package/dist/{deno-CfLMX4HF.mjs → deno-BgMEefEr.mjs} +1 -1
- package/dist/{docloader-CDmmYxYb.mjs → docloader-DEYs99FZ.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +122 -4
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +52 -9
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +111 -15
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/send.test.mjs +139 -4
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-CIeDMHxZ.cjs → federation-DzPUG_AP.cjs} +93 -17
- package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
- package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
- package/dist/{http-DWZZicb2.js → http-CgwqtxAy.js} +169 -7
- package/dist/{http-BR190IDB.mjs → http-CtidZS_t.mjs} +3 -3
- package/dist/{http-C3kkrvAE.cjs → http-Z3anvCd-.cjs} +180 -6
- package/dist/{key-BcA-AzPp.mjs → key-Cwbg5ggV.mjs} +169 -7
- package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
- package/dist/{kv-cache-DCItgZUM.cjs → kv-cache-BM2-w-Wq.cjs} +2 -2
- package/dist/{kv-cache-5d2HgB_-.js → kv-cache-CykuL0ya.js} +2 -2
- package/dist/{kv-cache-m962_cok.mjs → kv-cache-DHiR8yTf.mjs} +1 -1
- package/dist/{ld-QpPkmUjy.mjs → ld-CzhdWyTt.mjs} +3 -3
- package/dist/{metrics-Dkyzq7ml.mjs → metrics-CrqsHP0l.mjs} +1 -1
- package/dist/{middleware-BdKtWJK1.mjs → middleware-CZpYxSKD.mjs} +1 -1
- package/dist/{middleware-BNktRUnj.js → middleware-CeCj5caM.js} +101 -25
- package/dist/{middleware-BaZ32nCi.mjs → middleware-DCpiAsZi.mjs} +34 -23
- package/dist/{mod-DypxPS9H.d.ts → mod-C0BYc0OU.d.ts} +1 -1
- package/dist/{mod-CnGLNoVw.d.cts → mod-CbKDOnCt.d.cts} +1 -1
- package/dist/{mod-CLHkCwLm.d.cts → mod-CyiSjRkk.d.cts} +1 -1
- package/dist/{mod-CSH1Ee-I.d.ts → mod-D882_Xsg.d.ts} +1 -1
- package/dist/{mod-DzRXACM5.d.ts → mod-UDwMjuHM.d.ts} +1 -1
- package/dist/{mod-BZldCEU0.d.cts → mod-x9W0d0Pk.d.cts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/{owner-r_waTiQj.d.cts → owner-BF0L9cMt.d.cts} +11 -0
- package/dist/{owner-DGSfntI-.d.ts → owner-BSzr5-I8.d.ts} +11 -0
- package/dist/owner-BqVXJLW8.mjs +175 -0
- package/dist/{proof-DrWB_vel.js → proof-CUghqhAf.js} +122 -65
- package/dist/{proof-CYdLcTFg.cjs → proof-DHu5zI0Z.cjs} +122 -65
- package/dist/{proof-Mov6-gSw.mjs → proof-DL8Iid3a.mjs} +14 -11
- package/dist/{send-DB1mkFKO.mjs → send-6jS4xFsX.mjs} +68 -11
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +169 -2
- package/dist/sig/ld.test.mjs +55 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +103 -4
- package/dist/sig/proof.test.mjs +143 -2
- package/dist/{temporal-Yi0JAV9z.mjs → temporal-DcyzeytP.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +2 -1
- package/dist/{types-RXm5YxpC.cjs → types-Be2zbyeG.cjs} +94 -2
- package/dist/{types-Dtyf8lVj.js → types-C_cNlNVK.js} +78 -4
- package/dist/utils/docloader.test.mjs +27 -3
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
- package/dist/owner-B795xXwU.mjs +0 -120
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
4
|
+
import { n as version, t as name } from "./deno-BgMEefEr.mjs";
|
|
5
5
|
import { t as ActivityListenerSet } from "./activity-listener-C-BSbKI7.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { Router, RouterError, assertPath } from "@fedify/uri-template";
|
|
@@ -85,7 +85,7 @@ var FederationBuilderImpl = class {
|
|
|
85
85
|
* specified.
|
|
86
86
|
*/
|
|
87
87
|
async build(options) {
|
|
88
|
-
const { FederationImpl } = await import("./middleware-
|
|
88
|
+
const { FederationImpl } = await import("./middleware-CZpYxSKD.mjs");
|
|
89
89
|
const f = new FederationImpl(options);
|
|
90
90
|
const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
|
|
91
91
|
f.router = this.router.clone();
|
|
@@ -2,7 +2,81 @@ import "temporal-polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
|
-
import { UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
5
|
+
import { FetchError, UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
6
|
+
//#region src/utils/body.ts
|
|
7
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
8
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
9
|
+
/** A body exceeded the byte limit. @internal */
|
|
10
|
+
var BodyTooLargeError = class extends FetchError {
|
|
11
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
12
|
+
constructor(url, maxBytes) {
|
|
13
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
14
|
+
this.name = "BodyTooLargeError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
18
|
+
function validateBodySizeLimit(maxBytes) {
|
|
19
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
23
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
24
|
+
* @param message The response or request to read.
|
|
25
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
26
|
+
* @param url The URL to include in errors and logs.
|
|
27
|
+
* @returns The decoded body text.
|
|
28
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
32
|
+
validateBodySizeLimit(maxBytes);
|
|
33
|
+
const reader = message.body?.getReader();
|
|
34
|
+
const tooLarge = () => {
|
|
35
|
+
getLogger([
|
|
36
|
+
"fedify",
|
|
37
|
+
"runtime",
|
|
38
|
+
"body"
|
|
39
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
40
|
+
url: url.toString(),
|
|
41
|
+
maxBytes
|
|
42
|
+
});
|
|
43
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
const length = message.headers.get("Content-Length");
|
|
47
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
48
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
49
|
+
if (reader == null) return "";
|
|
50
|
+
let size = 0;
|
|
51
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) {
|
|
54
|
+
controller.close();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
size += value.byteLength;
|
|
58
|
+
if (size > maxBytes) tooLarge();
|
|
59
|
+
controller.enqueue(value);
|
|
60
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
61
|
+
try {
|
|
62
|
+
const chunks = [];
|
|
63
|
+
while (true) {
|
|
64
|
+
const { done, value } = await textReader.read();
|
|
65
|
+
if (done) break;
|
|
66
|
+
chunks.push(value);
|
|
67
|
+
}
|
|
68
|
+
return chunks.join("");
|
|
69
|
+
} finally {
|
|
70
|
+
textReader.releaseLock();
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
74
|
+
throw error;
|
|
75
|
+
} finally {
|
|
76
|
+
reader?.releaseLock();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
6
80
|
//#region src/nodeinfo/client.ts
|
|
7
81
|
const logger = getLogger([
|
|
8
82
|
"fedify",
|
|
@@ -83,7 +157,7 @@ async function getNodeInfo(url, options = {}) {
|
|
|
83
157
|
});
|
|
84
158
|
return;
|
|
85
159
|
}
|
|
86
|
-
const wellKnownRd = await wellKnownResponse
|
|
160
|
+
const wellKnownRd = JSON.parse(await readBoundedText(wellKnownResponse, MAX_BODY_SIZE, wellKnownUrl));
|
|
87
161
|
const link = wellKnownRd?.links?.find((link) => link != null && "rel" in link && (link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0" || link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1") && "href" in link && link.href != null);
|
|
88
162
|
if (link == null || link.href == null) {
|
|
89
163
|
logger.error("Failed to find a NodeInfo document link from {url}: {resourceDescriptor}", {
|
|
@@ -107,7 +181,7 @@ async function getNodeInfo(url, options = {}) {
|
|
|
107
181
|
});
|
|
108
182
|
return;
|
|
109
183
|
}
|
|
110
|
-
const data = await response
|
|
184
|
+
const data = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, nodeInfoUrl));
|
|
111
185
|
if (options.parse === "none") return data;
|
|
112
186
|
return parseNodeInfo(data, { tryBestEffort: options.parse === "best-effort" }) ?? void 0;
|
|
113
187
|
} catch (error) {
|
|
@@ -311,4 +385,4 @@ function parseUsage(data, options = {}) {
|
|
|
311
385
|
};
|
|
312
386
|
}
|
|
313
387
|
//#endregion
|
|
314
|
-
export { parseProtocol as a, parseUsage as c, parseOutboundService as i, parseInboundService as n, parseServices as o, parseNodeInfo as r, parseSoftware as s, getNodeInfo as t };
|
|
388
|
+
export { parseProtocol as a, parseUsage as c, readBoundedText as d, parseOutboundService as i, BodyTooLargeError as l, parseInboundService as n, parseServices as o, parseNodeInfo as r, parseSoftware as s, getNodeInfo as t, MAX_BODY_SIZE as u };
|
package/dist/compat/mod.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { Gt as ActivityTransformer, n as Context } from "../context-
|
|
2
|
+
import { Gt as ActivityTransformer, n as Context } from "../context-BeDnDuF3.cjs";
|
|
3
3
|
import { Activity } from "@fedify/vocab";
|
|
4
4
|
|
|
5
5
|
//#region src/compat/transformers.d.ts
|
package/dist/compat/mod.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { Gt as ActivityTransformer, n as Context } from "../context-
|
|
2
|
+
import { Gt as ActivityTransformer, n as Context } from "../context-uyRUWoZQ.js";
|
|
3
3
|
import { Activity } from "@fedify/vocab";
|
|
4
4
|
|
|
5
5
|
//#region src/compat/transformers.d.ts
|
|
@@ -5,7 +5,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
6
6
|
import { t as assertInstanceOf } from "../assert_instance_of-g3WcyYNR.mjs";
|
|
7
7
|
import { t as assert } from "../assert-CebeUf0C.mjs";
|
|
8
|
-
import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-
|
|
8
|
+
import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-DCpiAsZi.mjs";
|
|
9
9
|
import { Follow, Person } from "@fedify/vocab";
|
|
10
10
|
import { test } from "@fedify/fixture";
|
|
11
11
|
//#region src/compat/transformers.test.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { o as JsonValue, s as NodeInfo, t as GetNodeInfoOptions } from "./client-D-XP_Ccb.cjs";
|
|
3
|
-
import { o as VerifyRequestFailureReason, t as HttpMessageSignaturesSpec } from "./http-
|
|
4
|
-
import { n as GetKeyOwnerOptions } from "./owner-
|
|
3
|
+
import { o as VerifyRequestFailureReason, t as HttpMessageSignaturesSpec } from "./http-CP-7OdSq.cjs";
|
|
4
|
+
import { n as GetKeyOwnerOptions } from "./owner-BF0L9cMt.cjs";
|
|
5
5
|
import { n as KvStore, t as KvKey } from "./kv-BhNbN8Ed.cjs";
|
|
6
6
|
import { Activity, Actor, Collection, CryptographicKey, Hashtag, Link, LookupObjectOptions, Multikey, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
|
|
7
7
|
import { AuthenticatedDocumentLoaderFactory, DocumentLoader, DocumentLoaderFactory, GetUserAgentOptions } from "@fedify/vocab-runtime";
|
|
@@ -2052,7 +2052,8 @@ interface FederationOptions<TContextData> {
|
|
|
2052
2052
|
*/
|
|
2053
2053
|
authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory;
|
|
2054
2054
|
/**
|
|
2055
|
-
* Whether to allow
|
|
2055
|
+
* Whether to allow private network addresses in the document loader and
|
|
2056
|
+
* outbound activity delivery, including redirects.
|
|
2056
2057
|
*
|
|
2057
2058
|
* If turned on, {@link FederationOptions.documentLoader},
|
|
2058
2059
|
* {@link FederationOptions.contextLoader}, and
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { o as JsonValue, s as NodeInfo, t as GetNodeInfoOptions } from "./client-D-XP_Ccb.js";
|
|
3
|
-
import { o as VerifyRequestFailureReason, t as HttpMessageSignaturesSpec } from "./http-
|
|
4
|
-
import { n as GetKeyOwnerOptions } from "./owner-
|
|
3
|
+
import { o as VerifyRequestFailureReason, t as HttpMessageSignaturesSpec } from "./http-65PYi9jY.js";
|
|
4
|
+
import { n as GetKeyOwnerOptions } from "./owner-BSzr5-I8.js";
|
|
5
5
|
import { n as KvStore, t as KvKey } from "./kv-BhNbN8Ed.js";
|
|
6
6
|
import { Router } from "@fedify/uri-template";
|
|
7
7
|
import { Activity, Actor, Collection, CryptographicKey, Hashtag, Link, LookupObjectOptions, Multikey, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
|
|
@@ -2054,7 +2054,8 @@ interface FederationOptions<TContextData> {
|
|
|
2054
2054
|
*/
|
|
2055
2055
|
authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory;
|
|
2056
2056
|
/**
|
|
2057
|
-
* Whether to allow
|
|
2057
|
+
* Whether to allow private network addresses in the document loader and
|
|
2058
|
+
* outbound activity delivery, including redirects.
|
|
2058
2059
|
*
|
|
2059
2060
|
* If turned on, {@link FederationOptions.documentLoader},
|
|
2060
2061
|
* {@link FederationOptions.contextLoader}, and
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import {
|
|
5
|
-
import { n as doubleKnock } from "./http-
|
|
4
|
+
import { s as validateCryptoKey } from "./key-Cwbg5ggV.mjs";
|
|
5
|
+
import { n as doubleKnock } from "./http-CtidZS_t.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { curry } from "es-toolkit";
|
|
8
8
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
@@ -6,7 +6,7 @@ import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
|
6
6
|
import { t as assertExists } from "../assert_exists-DtzkXC47.mjs";
|
|
7
7
|
import { t as assertThrows } from "../assert_throws-BJ5wuvR2.mjs";
|
|
8
8
|
import "../std__assert-CxFT0MNt.mjs";
|
|
9
|
-
import { r as createFederationBuilder } from "../builder-
|
|
9
|
+
import { r as createFederationBuilder } from "../builder-Cts78G9N.mjs";
|
|
10
10
|
import { DisallowedOperatorError, DisallowedVarSpecModifierError, DuplicateRouteVariableError, RouteTemplateOptionsNotMatchedError, RouterError } from "@fedify/uri-template";
|
|
11
11
|
import { Activity, Note, Person } from "@fedify/vocab";
|
|
12
12
|
import { test } from "@fedify/fixture";
|
|
@@ -10,12 +10,14 @@ import "../std__assert-CxFT0MNt.mjs";
|
|
|
10
10
|
import { t as assertRejects } from "../assert_rejects-wFbSPn1O.mjs";
|
|
11
11
|
import { t as assert } from "../assert-CebeUf0C.mjs";
|
|
12
12
|
import { r as parseAcceptSignature } from "../accept-CPVqki78.mjs";
|
|
13
|
-
import {
|
|
13
|
+
import { a as generateCryptoKeyPair } from "../key-Cwbg5ggV.mjs";
|
|
14
|
+
import { s as signRequest } from "../http-CtidZS_t.mjs";
|
|
14
15
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
15
|
-
import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-
|
|
16
|
-
import { a as compactJsonLd, p as signJsonLd } from "../ld-
|
|
16
|
+
import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-DCpiAsZi.mjs";
|
|
17
|
+
import { a as compactJsonLd, p as signJsonLd } from "../ld-CzhdWyTt.mjs";
|
|
18
|
+
import { r as signObject } from "../proof-DL8Iid3a.mjs";
|
|
17
19
|
import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
|
|
18
|
-
import { Activity, Create, Note, Person, Tombstone } from "@fedify/vocab";
|
|
20
|
+
import { Activity, Create, CryptographicKey, Multikey, Note, Person, Tombstone } from "@fedify/vocab";
|
|
19
21
|
import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
20
22
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
21
23
|
//#region src/federation/handler.test.ts
|
|
@@ -4574,5 +4576,121 @@ test("handleInbox() challenge policy enabled + unverifiedActivityHandler throws
|
|
|
4574
4576
|
assertEquals(response.headers.get("Cache-Control"), "no-store", "Cache-Control: no-store must be set for challenge-response");
|
|
4575
4577
|
assertEquals(response.headers.get("Vary"), "Accept, Signature", "Vary header must include Accept and Signature");
|
|
4576
4578
|
});
|
|
4579
|
+
test("handleInbox() rejects forged key ownership", async () => {
|
|
4580
|
+
const impersonated = "https://example.com/person2";
|
|
4581
|
+
const attackerKeyId = new URL("https://attacker.example/key");
|
|
4582
|
+
const attackerMultikeyId = new URL("https://attacker.example/multikey");
|
|
4583
|
+
const { privateKey, publicKey } = await generateCryptoKeyPair("RSASSA-PKCS1-v1_5");
|
|
4584
|
+
const forgedKeyDocument = await new CryptographicKey({
|
|
4585
|
+
id: attackerKeyId,
|
|
4586
|
+
owner: new URL(impersonated),
|
|
4587
|
+
publicKey
|
|
4588
|
+
}).toJsonLd({ contextLoader: mockDocumentLoader });
|
|
4589
|
+
const ed25519 = await generateCryptoKeyPair("Ed25519");
|
|
4590
|
+
const forgedMultikeyDocument = await new Multikey({
|
|
4591
|
+
id: attackerMultikeyId,
|
|
4592
|
+
controller: new URL(impersonated),
|
|
4593
|
+
publicKey: ed25519.publicKey
|
|
4594
|
+
}).toJsonLd({ contextLoader: mockDocumentLoader });
|
|
4595
|
+
const documentLoader = (resource) => {
|
|
4596
|
+
if (resource === attackerKeyId.href) return Promise.resolve({
|
|
4597
|
+
contextUrl: null,
|
|
4598
|
+
documentUrl: resource,
|
|
4599
|
+
document: forgedKeyDocument
|
|
4600
|
+
});
|
|
4601
|
+
if (resource === attackerMultikeyId.href) return Promise.resolve({
|
|
4602
|
+
contextUrl: null,
|
|
4603
|
+
documentUrl: resource,
|
|
4604
|
+
document: forgedMultikeyDocument
|
|
4605
|
+
});
|
|
4606
|
+
return mockDocumentLoader(resource);
|
|
4607
|
+
};
|
|
4608
|
+
const federation = createFederation({ kv: new MemoryKvStore() });
|
|
4609
|
+
const inboxOptions = {
|
|
4610
|
+
kv: new MemoryKvStore(),
|
|
4611
|
+
kvPrefixes: {
|
|
4612
|
+
activityIdempotence: ["_fedify", "activityIdempotence"],
|
|
4613
|
+
publicKey: ["_fedify", "publicKey"],
|
|
4614
|
+
acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
|
|
4615
|
+
},
|
|
4616
|
+
actorDispatcher: ((_ctx, identifier) => identifier === "someone" ? new Person({ name: "Someone" }) : null),
|
|
4617
|
+
inboxListeners: new ActivityListenerSet(),
|
|
4618
|
+
onNotFound: () => new Response("Not found", { status: 404 }),
|
|
4619
|
+
signatureTimeWindow: { minutes: 5 },
|
|
4620
|
+
skipSignatureVerification: false
|
|
4621
|
+
};
|
|
4622
|
+
const handle = async (request) => {
|
|
4623
|
+
const context = createRequestContext({
|
|
4624
|
+
federation,
|
|
4625
|
+
request,
|
|
4626
|
+
url: new URL(request.url),
|
|
4627
|
+
data: void 0,
|
|
4628
|
+
documentLoader,
|
|
4629
|
+
contextLoader: mockDocumentLoader
|
|
4630
|
+
});
|
|
4631
|
+
return await handleInbox(request, {
|
|
4632
|
+
recipient: null,
|
|
4633
|
+
context,
|
|
4634
|
+
inboxContextFactory(_activity) {
|
|
4635
|
+
return createInboxContext({
|
|
4636
|
+
...context,
|
|
4637
|
+
clone: void 0
|
|
4638
|
+
});
|
|
4639
|
+
},
|
|
4640
|
+
...inboxOptions
|
|
4641
|
+
});
|
|
4642
|
+
};
|
|
4643
|
+
const activity = {
|
|
4644
|
+
"@context": [
|
|
4645
|
+
"https://www.w3.org/ns/activitystreams",
|
|
4646
|
+
"https://w3id.org/identity/v1",
|
|
4647
|
+
"https://w3id.org/security/v1",
|
|
4648
|
+
"https://w3id.org/security/data-integrity/v1"
|
|
4649
|
+
],
|
|
4650
|
+
id: "https://attacker.example/activities/1",
|
|
4651
|
+
type: "Create",
|
|
4652
|
+
actor: impersonated,
|
|
4653
|
+
object: {
|
|
4654
|
+
id: "https://attacker.example/notes/1",
|
|
4655
|
+
type: "Note",
|
|
4656
|
+
attributedTo: impersonated,
|
|
4657
|
+
content: "Hello World!"
|
|
4658
|
+
}
|
|
4659
|
+
};
|
|
4660
|
+
const httpSignedResponse = await handle(await signRequest(new Request("https://example.com/", {
|
|
4661
|
+
method: "POST",
|
|
4662
|
+
body: JSON.stringify(activity)
|
|
4663
|
+
}), privateKey, attackerKeyId));
|
|
4664
|
+
const ldSignedResponse = await handle(new Request("https://example.com/", {
|
|
4665
|
+
method: "POST",
|
|
4666
|
+
body: JSON.stringify(await signJsonLd(activity, privateKey, attackerKeyId, { contextLoader: mockDocumentLoader }))
|
|
4667
|
+
}));
|
|
4668
|
+
const signedObject = await signObject(await Create.fromJsonLd(activity, {
|
|
4669
|
+
documentLoader,
|
|
4670
|
+
contextLoader: mockDocumentLoader
|
|
4671
|
+
}), ed25519.privateKey, attackerMultikeyId, {
|
|
4672
|
+
documentLoader,
|
|
4673
|
+
contextLoader: mockDocumentLoader,
|
|
4674
|
+
context: ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"]
|
|
4675
|
+
});
|
|
4676
|
+
const proofSignedResponse = await handle(new Request("https://example.com/", {
|
|
4677
|
+
method: "POST",
|
|
4678
|
+
body: JSON.stringify(await signedObject.toJsonLd({
|
|
4679
|
+
format: "compact",
|
|
4680
|
+
contextLoader: mockDocumentLoader,
|
|
4681
|
+
context: ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"]
|
|
4682
|
+
}))
|
|
4683
|
+
}));
|
|
4684
|
+
assertEquals([
|
|
4685
|
+
httpSignedResponse.status,
|
|
4686
|
+
ldSignedResponse.status,
|
|
4687
|
+
proofSignedResponse.status
|
|
4688
|
+
], [
|
|
4689
|
+
401,
|
|
4690
|
+
401,
|
|
4691
|
+
401
|
|
4692
|
+
]);
|
|
4693
|
+
assertEquals(await httpSignedResponse.text(), "Failed to verify the request signature.");
|
|
4694
|
+
});
|
|
4577
4695
|
//#endregion
|
|
4578
4696
|
export {};
|
|
@@ -5,8 +5,8 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
6
6
|
import "../std__assert-CxFT0MNt.mjs";
|
|
7
7
|
import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
|
|
8
|
-
import { o as createFederation } from "../middleware-
|
|
9
|
-
import { r as signObject } from "../proof-
|
|
8
|
+
import { o as createFederation } from "../middleware-DCpiAsZi.mjs";
|
|
9
|
+
import { r as signObject } from "../proof-DL8Iid3a.mjs";
|
|
10
10
|
import { Create, Follow, Person } from "@fedify/vocab";
|
|
11
11
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
12
12
|
//#region src/federation/idempotency.test.ts
|
|
@@ -6,7 +6,7 @@ import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
|
|
|
6
6
|
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
7
7
|
import { t as assertInstanceOf } from "../assert_instance_of-g3WcyYNR.mjs";
|
|
8
8
|
import { t as assert } from "../assert-CebeUf0C.mjs";
|
|
9
|
-
import { t as KvKeyCache } from "../keycache-
|
|
9
|
+
import { t as KvKeyCache } from "../keycache-BUDHB5rY.mjs";
|
|
10
10
|
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
11
11
|
import { test } from "@fedify/fixture";
|
|
12
12
|
//#region src/federation/keycache.test.ts
|
|
@@ -14,25 +14,41 @@ test("KvKeyCache.set()", async () => {
|
|
|
14
14
|
const kv = new MemoryKvStore();
|
|
15
15
|
const cache = new KvKeyCache(kv, ["pk"]);
|
|
16
16
|
await cache.set(new URL("https://example.com/key"), new CryptographicKey({ id: new URL("https://example.com/key") }));
|
|
17
|
-
assertEquals(await kv.get([
|
|
17
|
+
assertEquals(await kv.get([
|
|
18
|
+
"pk",
|
|
19
|
+
"2",
|
|
20
|
+
"https://example.com/key"
|
|
21
|
+
]), {
|
|
18
22
|
"@context": "https://w3id.org/security/v1",
|
|
19
23
|
id: "https://example.com/key",
|
|
20
24
|
type: "CryptographicKey"
|
|
21
25
|
});
|
|
22
26
|
await cache.set(new URL("https://example.com/key2"), new Multikey({ id: new URL("https://example.com/key2") }));
|
|
23
|
-
assertEquals(await kv.get([
|
|
27
|
+
assertEquals(await kv.get([
|
|
28
|
+
"pk",
|
|
29
|
+
"2",
|
|
30
|
+
"https://example.com/key2"
|
|
31
|
+
]), {
|
|
24
32
|
"@context": "https://w3id.org/security/multikey/v1",
|
|
25
33
|
id: "https://example.com/key2",
|
|
26
34
|
type: "Multikey"
|
|
27
35
|
});
|
|
28
36
|
await cache.set(new URL("https://example.com/null"), null);
|
|
29
37
|
assert(cache.nullKeys.has("https://example.com/null"));
|
|
30
|
-
assertEquals(await kv.get([
|
|
38
|
+
assertEquals(await kv.get([
|
|
39
|
+
"pk",
|
|
40
|
+
"2",
|
|
41
|
+
"https://example.com/null"
|
|
42
|
+
]), null);
|
|
31
43
|
});
|
|
32
44
|
test("KvKeyCache.get()", async () => {
|
|
33
45
|
const kv = new MemoryKvStore();
|
|
34
46
|
const cache = new KvKeyCache(kv, ["pk"]);
|
|
35
|
-
await kv.set([
|
|
47
|
+
await kv.set([
|
|
48
|
+
"pk",
|
|
49
|
+
"2",
|
|
50
|
+
"https://example.com/key"
|
|
51
|
+
], {
|
|
36
52
|
"@context": "https://w3id.org/security/v1",
|
|
37
53
|
id: "https://example.com/key",
|
|
38
54
|
type: "CryptographicKey"
|
|
@@ -40,7 +56,11 @@ test("KvKeyCache.get()", async () => {
|
|
|
40
56
|
const cryptoKey = await cache.get(new URL("https://example.com/key"));
|
|
41
57
|
assertInstanceOf(cryptoKey, CryptographicKey);
|
|
42
58
|
assertEquals(cryptoKey?.id?.href, "https://example.com/key");
|
|
43
|
-
await kv.set([
|
|
59
|
+
await kv.set([
|
|
60
|
+
"pk",
|
|
61
|
+
"2",
|
|
62
|
+
"https://example.com/key2"
|
|
63
|
+
], {
|
|
44
64
|
"@context": "https://w3id.org/security/multikey/v1",
|
|
45
65
|
id: "https://example.com/key2",
|
|
46
66
|
type: "Multikey"
|
|
@@ -50,7 +70,11 @@ test("KvKeyCache.get()", async () => {
|
|
|
50
70
|
assertEquals(multikey?.id?.href, "https://example.com/key2");
|
|
51
71
|
cache.nullKeys.set("https://example.com/null", Temporal.Now.instant().add(Temporal.Duration.from({ minutes: 10 })));
|
|
52
72
|
assertEquals(await cache.get(new URL("https://example.com/null")), null);
|
|
53
|
-
await kv.set([
|
|
73
|
+
await kv.set([
|
|
74
|
+
"pk",
|
|
75
|
+
"2",
|
|
76
|
+
"https://example.com/null2"
|
|
77
|
+
], null);
|
|
54
78
|
assertEquals(await new KvKeyCache(kv, ["pk"]).get(new URL("https://example.com/null2")), null);
|
|
55
79
|
});
|
|
56
80
|
test("KvKeyCache fetch error metadata", async () => {
|
|
@@ -105,10 +129,18 @@ test("KvKeyCache cached keys expire after keyTtl", async () => {
|
|
|
105
129
|
const cache = new KvKeyCache(kv, ["pk"], { keyTtl: Temporal.Duration.from({ days: 30 }) });
|
|
106
130
|
const keyId = new URL("https://example.com/key");
|
|
107
131
|
await cache.set(keyId, new CryptographicKey({ id: keyId }));
|
|
108
|
-
assert(await kv.get([
|
|
132
|
+
assert(await kv.get([
|
|
133
|
+
"pk",
|
|
134
|
+
"2",
|
|
135
|
+
keyId.href
|
|
136
|
+
]) != null);
|
|
109
137
|
assertInstanceOf(await cache.get(keyId), CryptographicKey);
|
|
110
138
|
kv.advance({ days: 31 });
|
|
111
|
-
assertEquals(await kv.get([
|
|
139
|
+
assertEquals(await kv.get([
|
|
140
|
+
"pk",
|
|
141
|
+
"2",
|
|
142
|
+
keyId.href
|
|
143
|
+
]), void 0);
|
|
112
144
|
assertEquals(await cache.get(keyId), void 0);
|
|
113
145
|
assertEquals(cache.nullKeys.has(keyId.href), false);
|
|
114
146
|
await cache.set(keyId, new CryptographicKey({ id: keyId }));
|
|
@@ -116,5 +148,16 @@ test("KvKeyCache cached keys expire after keyTtl", async () => {
|
|
|
116
148
|
assertInstanceOf(refetched, CryptographicKey);
|
|
117
149
|
assertEquals(refetched.id?.href, keyId.href);
|
|
118
150
|
});
|
|
151
|
+
test("KvKeyCache.get() ignores entries from an earlier generation", async () => {
|
|
152
|
+
const kv = new MemoryKvStore();
|
|
153
|
+
const cache = new KvKeyCache(kv, ["pk"]);
|
|
154
|
+
await kv.set(["pk", "https://example.com/key"], {
|
|
155
|
+
"@context": "https://w3id.org/security/v1",
|
|
156
|
+
id: "https://example.com/key",
|
|
157
|
+
owner: "https://example.com/impersonated",
|
|
158
|
+
type: "CryptographicKey"
|
|
159
|
+
});
|
|
160
|
+
assertEquals(await cache.get(new URL("https://example.com/key")), void 0);
|
|
161
|
+
});
|
|
119
162
|
//#endregion
|
|
120
163
|
export {};
|
|
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-CBlRfA-_.mjs";
|
|
5
5
|
import "../std__assert-CxFT0MNt.mjs";
|
|
6
6
|
import { t as assertRejects } from "../assert_rejects-wFbSPn1O.mjs";
|
|
7
|
-
import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-
|
|
7
|
+
import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-CrqsHP0l.mjs";
|
|
8
8
|
import { createTestMeterProvider, test } from "@fedify/fixture";
|
|
9
9
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
10
10
|
import { DataPointType, MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";
|