@fedify/fedify 2.1.29 → 2.1.31
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-Mg2G3qj8.mjs → builder-isMRh_I6.mjs} +3 -3
- package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
- package/dist/{client-CjDMsn4a.mjs → client-BZr7TGyu.mjs} +113 -64
- package/dist/{client-CSddvgWN.d.ts → client-jE7oGvRh.d.ts} +0 -1
- 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-DLLaDau8.d.ts → context-C0uGVzgk.d.ts} +28 -2
- package/dist/{context-BNwv9Q1v.d.cts → context-CLaLxi49.d.cts} +28 -2
- package/dist/{deno-BiYv5RT5.mjs → deno-CiaJqTcu.mjs} +1 -1
- package/dist/docloader-DOrwedT7.mjs +124 -0
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +6 -6
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/keycache.test.mjs +18 -7
- package/dist/federation/middleware.test.mjs +380 -24
- 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 +445 -70
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{http-Dl00HXr4.d.ts → http-9IFUxdkp.d.cts} +1 -2
- package/dist/{http-C1ga9WQu.mjs → http-A9mP2uM7.mjs} +194 -96
- package/dist/{http-Cginz7L0.cjs → http-CRiVlZd5.cjs} +188 -105
- package/dist/{http-B3rSb2GQ.js → http-Crml3gei.js} +187 -104
- package/dist/{http-DqZhyEso.d.cts → http-SbfezERn.d.ts} +1 -2
- package/dist/{inbox-BWzL0xMU.mjs → inbox-KESLudq4.mjs} +1 -1
- package/dist/{key-O-ATwIGS.mjs → key-Dg6-DV_O.mjs} +17 -3
- package/dist/{keycache-RFlZObU3.mjs → keycache-DahCaG_m.mjs} +1 -1
- package/dist/{kv-cache-BdylAE9w.cjs → kv-cache-Cqmm-yQa.cjs} +59 -15
- package/dist/{kv-cache-8Kg2CHiD.js → kv-cache-DQcyZL5T.js} +60 -16
- package/dist/{ld-C-fCGM-z.mjs → ld-COjDN6Nb.mjs} +2 -2
- package/dist/{middleware-CVRFS7PZ.cjs → middleware-BbgR7xOI.cjs} +185 -60
- package/dist/{middleware-VvrqPwHL.cjs → middleware-BtDQUCcz.cjs} +1 -1
- package/dist/{middleware-C9UCWOqw.mjs → middleware-COFlKeYn.mjs} +79 -39
- package/dist/{middleware-CxgFiKI3.mjs → middleware-CuQvczZd.mjs} +1 -1
- package/dist/{middleware-41MkquTV.js → middleware-DIW5S_5L.js} +185 -60
- package/dist/{mod-DNEgTI8M.d.ts → mod-BRnwh2oy.d.ts} +2 -7
- package/dist/{mod-BLDHhuW0.d.ts → mod-C7w-QVUv.d.ts} +1 -1
- package/dist/{mod-CWJNbyju.d.ts → mod-CFRocFeQ.d.ts} +1 -1
- package/dist/{mod-BmlfRBmU.d.cts → mod-CGUH8Ujp.d.cts} +1 -1
- package/dist/{mod-Cn9GJ5ex.d.cts → mod-Co7LEK9X.d.cts} +2 -7
- package/dist/{mod-DiuFTYUt.d.cts → mod-LOEOKbYd.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 +337 -19
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/otel/exporter.test.mjs +292 -30
- package/dist/otel/mod.cjs +36 -30
- package/dist/otel/mod.d.cts +13 -0
- package/dist/otel/mod.d.ts +13 -0
- package/dist/otel/mod.js +36 -30
- package/dist/{owner-C8IVASD3.mjs → owner-CFHq61cq.mjs} +2 -2
- package/dist/{proof-BbHMAkPG.cjs → proof-0JkSC_pT.cjs} +1 -1
- package/dist/{proof-cGfRPTiV.js → proof-DzksJFK7.js} +1 -1
- package/dist/{proof-CUdcBgvg.mjs → proof-KSHlbPCG.mjs} +2 -2
- package/dist/{send-BoV4dyVO.mjs → send-DaDe2YCb.mjs} +117 -32
- package/dist/sig/http.test.mjs +507 -3
- package/dist/sig/key.test.mjs +197 -3
- package/dist/sig/ld.test.mjs +47 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +46 -1
- package/dist/sig/proof.test.mjs +52 -1
- package/dist/{temporal-BJlnku_d.mjs → temporal-DNGH8o2E.mjs} +1 -1
- package/dist/testing/mod.d.mts +26 -1
- package/dist/{types-miAYfiGO.js → types-B1_pRFE_.js} +112 -63
- package/dist/{types-DRPjmHVV.cjs → types-DOvyymM4.cjs} +111 -62
- package/dist/utils/docloader.test.mjs +380 -32
- 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/docloader-DrxXBFL8.mjs +0 -80
|
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
|
|
|
10
10
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
11
11
|
//#region deno.json
|
|
12
12
|
var name = "@fedify/fedify";
|
|
13
|
-
var version = "2.1.
|
|
13
|
+
var version = "2.1.31";
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/sig/accept.ts
|
|
16
16
|
/**
|
|
@@ -291,16 +291,23 @@ async function importJwk(jwk, type) {
|
|
|
291
291
|
/**
|
|
292
292
|
* Fetches the document that the given actor URI dereferences to, and returns
|
|
293
293
|
* the actor only if the host that served the document is authoritative for
|
|
294
|
-
* the id the document claims.
|
|
294
|
+
* the id the document claims, and that id is the actor that was asked for.
|
|
295
295
|
*
|
|
296
296
|
* Only the origin that serves an actor id can speak for it. Without that
|
|
297
297
|
* rule any host could serve a document describing somebody else's actor—and
|
|
298
298
|
* listing its own keys as that actor's.
|
|
299
299
|
*
|
|
300
|
+
* The returned actor always has exactly the requested `actorId` as its `id`.
|
|
301
|
+
* A document loader follows redirects and alternate document links, so the
|
|
302
|
+
* document an actor id leads to may describe a different actor altogether,
|
|
303
|
+
* on the same origin or on another; accepting it would attribute that other
|
|
304
|
+
* actor's keys to the requested one. See GHSA-8863-8468-7hq7.
|
|
305
|
+
*
|
|
300
306
|
* @param actorId The URI of the actor to fetch.
|
|
301
307
|
* @param options Options for fetching the document.
|
|
302
308
|
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
303
|
-
* actor,
|
|
309
|
+
* actor, belongs to another origin, or describes an actor other
|
|
310
|
+
* than the requested one.
|
|
304
311
|
* @internal
|
|
305
312
|
*/
|
|
306
313
|
async function fetchActorDocument(actorId, options = {}) {
|
|
@@ -355,6 +362,13 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
|
|
|
355
362
|
});
|
|
356
363
|
return null;
|
|
357
364
|
}
|
|
365
|
+
if (object.id.href !== actorId.href) {
|
|
366
|
+
logger.debug("The document fetched for the actor {actorId} is the actor {documentActorId} instead; refusing to treat it as the requested actor's document.", {
|
|
367
|
+
actorId: actorId.href,
|
|
368
|
+
documentActorId: object.id.href
|
|
369
|
+
});
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
358
372
|
return object;
|
|
359
373
|
}
|
|
360
374
|
/**
|
|
@@ -1583,10 +1597,10 @@ async function createRedirectRequest(request, location, body, validateRedirect)
|
|
|
1583
1597
|
cache: request.cache
|
|
1584
1598
|
});
|
|
1585
1599
|
}
|
|
1586
|
-
async function fetchDoubleKnockRequest(request, signedRequest, signal) {
|
|
1600
|
+
async function fetchDoubleKnockRequest(session, request, signedRequest, signal) {
|
|
1587
1601
|
const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
|
|
1588
1602
|
for (let attempt = 1;; attempt++) try {
|
|
1589
|
-
return await fetch(signedRequest, {
|
|
1603
|
+
return await session.fetch(signedRequest, {
|
|
1590
1604
|
redirect: "manual",
|
|
1591
1605
|
signal
|
|
1592
1606
|
});
|
|
@@ -1637,117 +1651,186 @@ function getAbortReason(signal) {
|
|
|
1637
1651
|
return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
1638
1652
|
}
|
|
1639
1653
|
/**
|
|
1640
|
-
* Performs a double-knock request
|
|
1641
|
-
*
|
|
1642
|
-
*
|
|
1654
|
+
* Performs a double-knock request like {@link doubleKnock}, opening a session
|
|
1655
|
+
* for every hop with the given function, and passes the final response to
|
|
1656
|
+
* the given function. The final session is closed after `consume` settles,
|
|
1657
|
+
* so `consume` must read or cancel the response body before it returns.
|
|
1658
|
+
* This is not exported from any entry point.
|
|
1643
1659
|
* @param request The request to send.
|
|
1644
1660
|
* @param identity The identity to use for signing the request.
|
|
1645
1661
|
* @param options The options for double-knock requests.
|
|
1646
|
-
* @
|
|
1647
|
-
*
|
|
1662
|
+
* @param openHop The function to open a session for each hop. The session of
|
|
1663
|
+
* a hop is closed before the next hop is opened.
|
|
1664
|
+
* @param consume The function to consume the final response.
|
|
1665
|
+
* @returns The value returned by `consume`.
|
|
1648
1666
|
*/
|
|
1649
|
-
async function
|
|
1650
|
-
|
|
1667
|
+
async function doubleKnockScoped(request, identity, options, openHop, consume) {
|
|
1668
|
+
const { response, session } = await doubleKnockCore(request, identity, options, openHop);
|
|
1669
|
+
try {
|
|
1670
|
+
return await consume(response);
|
|
1671
|
+
} catch (error) {
|
|
1672
|
+
getLogger([
|
|
1673
|
+
"fedify",
|
|
1674
|
+
"sig",
|
|
1675
|
+
"http"
|
|
1676
|
+
]).debug("Failed to consume the response from {url}; closing the session: {error}", {
|
|
1677
|
+
url: request.url,
|
|
1678
|
+
error
|
|
1679
|
+
});
|
|
1680
|
+
throw error;
|
|
1681
|
+
} finally {
|
|
1682
|
+
await session.close();
|
|
1683
|
+
}
|
|
1651
1684
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1666
|
-
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
1667
|
-
const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
|
|
1668
|
-
if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
|
|
1669
|
-
return doubleKnockInternal(redirectRequest, identity, {
|
|
1670
|
-
...options,
|
|
1671
|
-
body
|
|
1672
|
-
}, redirected + 1, visited);
|
|
1673
|
-
} else if (response.status === 400 || response.status === 401 || response.status > 401) {
|
|
1674
|
-
const logger = getLogger([
|
|
1685
|
+
function isRedirectResponse(response) {
|
|
1686
|
+
return response.status >= 300 && response.status < 400 && response.headers.has("Location");
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Starts cancelling the body of a response that is not passed to the caller,
|
|
1690
|
+
* without waiting for it to settle. The cancellation of a branch of a tee
|
|
1691
|
+
* (e.g., a body cloned by a fetch mock) settles only after the other branch
|
|
1692
|
+
* is cancelled too, per the Streams standard, so awaiting it could block
|
|
1693
|
+
* forever. Failures are only logged.
|
|
1694
|
+
*/
|
|
1695
|
+
function cancelResponseBody(response, url) {
|
|
1696
|
+
response.body?.cancel().catch((error) => {
|
|
1697
|
+
getLogger([
|
|
1675
1698
|
"fedify",
|
|
1676
1699
|
"sig",
|
|
1677
1700
|
"http"
|
|
1678
|
-
])
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1701
|
+
]).debug("Failed to cancel the response body from {url}: {error}", {
|
|
1702
|
+
url,
|
|
1703
|
+
error
|
|
1704
|
+
});
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
async function doubleKnockCore(request, identity, options, openHop) {
|
|
1708
|
+
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
1709
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1710
|
+
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
1711
|
+
let session = await openHop(request.url);
|
|
1712
|
+
try {
|
|
1713
|
+
for (let redirected = 0;; redirected++) {
|
|
1714
|
+
visited.add(request.url);
|
|
1715
|
+
const origin = new URL(request.url).origin;
|
|
1716
|
+
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
1717
|
+
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1718
|
+
spec: firstTrySpec,
|
|
1719
|
+
tracerProvider,
|
|
1720
|
+
body
|
|
1721
|
+
});
|
|
1722
|
+
log?.(signedRequest);
|
|
1723
|
+
let response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
|
|
1724
|
+
const firstRedirected = isRedirectResponse(response);
|
|
1725
|
+
if (!firstRedirected && (response.status === 400 || response.status === 401 || response.status > 401)) {
|
|
1726
|
+
const logger = getLogger([
|
|
1727
|
+
"fedify",
|
|
1728
|
+
"sig",
|
|
1729
|
+
"http"
|
|
1730
|
+
]);
|
|
1731
|
+
const acceptSigHeader = response.headers.get("Accept-Signature");
|
|
1732
|
+
if (acceptSigHeader != null) {
|
|
1733
|
+
const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
|
|
1734
|
+
const localKeyId = identity.keyId.href;
|
|
1735
|
+
const localAlg = "rsa-v1_5-sha256";
|
|
1736
|
+
let fulfilled = false;
|
|
1737
|
+
let challengeRequest;
|
|
1738
|
+
for (const entry of entries) {
|
|
1739
|
+
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
1740
|
+
if (rfc9421 == null) continue;
|
|
1741
|
+
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
1742
|
+
label: rfc9421.label,
|
|
1743
|
+
components: rfc9421.components
|
|
1744
|
+
});
|
|
1745
|
+
try {
|
|
1746
|
+
challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
|
|
1747
|
+
spec: "rfc9421",
|
|
1748
|
+
tracerProvider,
|
|
1749
|
+
body,
|
|
1750
|
+
rfc9421
|
|
1751
|
+
});
|
|
1752
|
+
fulfilled = true;
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
|
|
1755
|
+
label: entry.label,
|
|
1756
|
+
error
|
|
1757
|
+
});
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
if (fulfilled && challengeRequest != null) {
|
|
1761
|
+
cancelResponseBody(response, request.url);
|
|
1762
|
+
signedRequest = challengeRequest;
|
|
1763
|
+
log?.(signedRequest);
|
|
1764
|
+
response = await session.fetch(signedRequest, {
|
|
1765
|
+
redirect: "manual",
|
|
1766
|
+
signal
|
|
1767
|
+
});
|
|
1768
|
+
}
|
|
1769
|
+
if (fulfilled && !isRedirectResponse(response)) {
|
|
1770
|
+
if (response.status < 300) {
|
|
1771
|
+
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
1772
|
+
return {
|
|
1773
|
+
response,
|
|
1774
|
+
session
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
if (response.status !== 400 && response.status !== 401) return {
|
|
1778
|
+
response,
|
|
1779
|
+
session
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
if (!isRedirectResponse(response)) {
|
|
1784
|
+
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
1785
|
+
getLogger([
|
|
1786
|
+
"fedify",
|
|
1787
|
+
"sig",
|
|
1788
|
+
"http"
|
|
1789
|
+
]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
1790
|
+
spec: firstTrySpec,
|
|
1791
|
+
secondSpec: spec,
|
|
1792
|
+
status: response.status,
|
|
1793
|
+
statusText: response.statusText
|
|
1699
1794
|
});
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1795
|
+
cancelResponseBody(response, request.url);
|
|
1796
|
+
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1797
|
+
spec,
|
|
1798
|
+
tracerProvider,
|
|
1799
|
+
body
|
|
1705
1800
|
});
|
|
1801
|
+
log?.(signedRequest);
|
|
1802
|
+
response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
|
|
1803
|
+
if (!isRedirectResponse(response) && response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
|
|
1706
1804
|
}
|
|
1707
|
-
}
|
|
1708
|
-
if (
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
});
|
|
1715
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
|
|
1716
|
-
...options,
|
|
1717
|
-
body
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1720
|
-
if (fulfilled && response.status < 300) {
|
|
1721
|
-
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
1722
|
-
return response;
|
|
1723
|
-
}
|
|
1724
|
-
if (fulfilled && response.status !== 400 && response.status !== 401) return response;
|
|
1725
|
-
}
|
|
1726
|
-
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
1727
|
-
logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
1728
|
-
spec: firstTrySpec,
|
|
1729
|
-
secondSpec: spec,
|
|
1730
|
-
status: response.status,
|
|
1731
|
-
statusText: response.statusText
|
|
1732
|
-
});
|
|
1733
|
-
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1734
|
-
spec,
|
|
1735
|
-
tracerProvider,
|
|
1736
|
-
body
|
|
1737
|
-
});
|
|
1738
|
-
log?.(signedRequest);
|
|
1739
|
-
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
1740
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1805
|
+
} else if (!firstRedirected) await specDeterminer?.rememberSpec(origin, firstTrySpec);
|
|
1806
|
+
if (!isRedirectResponse(response)) return {
|
|
1807
|
+
response,
|
|
1808
|
+
session
|
|
1809
|
+
};
|
|
1810
|
+
cancelResponseBody(response, request.url);
|
|
1811
|
+
await session.close();
|
|
1741
1812
|
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
1742
|
-
const
|
|
1813
|
+
const location = response.headers.get("Location");
|
|
1814
|
+
const redirectRequest = await createRedirectRequest(request, location, body, options.validateRedirect);
|
|
1743
1815
|
if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1816
|
+
request = redirectRequest;
|
|
1817
|
+
session = await openHop(request.url);
|
|
1818
|
+
}
|
|
1819
|
+
} catch (error) {
|
|
1820
|
+
try {
|
|
1821
|
+
await session.close();
|
|
1822
|
+
} catch (closeError) {
|
|
1823
|
+
getLogger([
|
|
1824
|
+
"fedify",
|
|
1825
|
+
"sig",
|
|
1826
|
+
"http"
|
|
1827
|
+
]).debug("Failed to close the session for {url}: {error}", {
|
|
1828
|
+
url: request.url,
|
|
1829
|
+
error: closeError
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
throw error;
|
|
1833
|
+
}
|
|
1751
1834
|
}
|
|
1752
1835
|
/**
|
|
1753
1836
|
* Performs a timing-safe equality comparison between two `Uint8Array` values.
|
|
@@ -1777,4 +1860,4 @@ function timingSafeEqual(a, b) {
|
|
|
1777
1860
|
return result === 0;
|
|
1778
1861
|
}
|
|
1779
1862
|
//#endregion
|
|
1780
|
-
export { validateAcceptSignature as _, verifyRequestDetailed as a, fetchKey as c, importJwk as d, validateCryptoKey as f, parseAcceptSignature as g, fulfillAcceptSignature as h, verifyRequest as i, fetchKeyDetailed as l, formatAcceptSignature as m, parseRfc9421SignatureInput as n, exportJwk as o, verifyKeyOwnership as p, signRequest as r, fetchActorDocument as s,
|
|
1863
|
+
export { validateAcceptSignature as _, verifyRequestDetailed as a, fetchKey as c, importJwk as d, validateCryptoKey as f, parseAcceptSignature as g, fulfillAcceptSignature as h, verifyRequest as i, fetchKeyDetailed as l, formatAcceptSignature as m, parseRfc9421SignatureInput as n, exportJwk as o, verifyKeyOwnership as p, signRequest as r, fetchActorDocument as s, doubleKnockScoped as t, generateCryptoKeyPair as u, name as v, version as y };
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
2
|
import { Actor, CryptographicKey, Multikey } from "@fedify/vocab";
|
|
3
|
-
import { DocumentLoader } from "@fedify/vocab-runtime";
|
|
4
3
|
import { TracerProvider } from "@opentelemetry/api";
|
|
5
|
-
|
|
4
|
+
import { DocumentLoader } from "@fedify/vocab-runtime";
|
|
6
5
|
//#region src/sig/key.d.ts
|
|
7
6
|
/**
|
|
8
7
|
* Generates a key pair which is appropriate for Fedify.
|
|
@@ -1,7 +1,7 @@
|
|
|
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-
|
|
4
|
+
import { n as version, t as name } from "./deno-CiaJqTcu.mjs";
|
|
5
5
|
import { Activity, getTypeId } from "@fedify/vocab";
|
|
6
6
|
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
@@ -1,7 +1,7 @@
|
|
|
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-
|
|
4
|
+
import { n as version, t as name } from "./deno-CiaJqTcu.mjs";
|
|
5
5
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
6
6
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
7
7
|
import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
@@ -146,16 +146,23 @@ async function importJwk(jwk, type) {
|
|
|
146
146
|
/**
|
|
147
147
|
* Fetches the document that the given actor URI dereferences to, and returns
|
|
148
148
|
* the actor only if the host that served the document is authoritative for
|
|
149
|
-
* the id the document claims.
|
|
149
|
+
* the id the document claims, and that id is the actor that was asked for.
|
|
150
150
|
*
|
|
151
151
|
* Only the origin that serves an actor id can speak for it. Without that
|
|
152
152
|
* rule any host could serve a document describing somebody else's actor—and
|
|
153
153
|
* listing its own keys as that actor's.
|
|
154
154
|
*
|
|
155
|
+
* The returned actor always has exactly the requested `actorId` as its `id`.
|
|
156
|
+
* A document loader follows redirects and alternate document links, so the
|
|
157
|
+
* document an actor id leads to may describe a different actor altogether,
|
|
158
|
+
* on the same origin or on another; accepting it would attribute that other
|
|
159
|
+
* actor's keys to the requested one. See GHSA-8863-8468-7hq7.
|
|
160
|
+
*
|
|
155
161
|
* @param actorId The URI of the actor to fetch.
|
|
156
162
|
* @param options Options for fetching the document.
|
|
157
163
|
* @returns The actor, or `null` if the document cannot be fetched, is not an
|
|
158
|
-
* actor,
|
|
164
|
+
* actor, belongs to another origin, or describes an actor other
|
|
165
|
+
* than the requested one.
|
|
159
166
|
* @internal
|
|
160
167
|
*/
|
|
161
168
|
async function fetchActorDocument(actorId, options = {}) {
|
|
@@ -210,6 +217,13 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
|
|
|
210
217
|
});
|
|
211
218
|
return null;
|
|
212
219
|
}
|
|
220
|
+
if (object.id.href !== actorId.href) {
|
|
221
|
+
logger.debug("The document fetched for the actor {actorId} is the actor {documentActorId} instead; refusing to treat it as the requested actor's document.", {
|
|
222
|
+
actorId: actorId.href,
|
|
223
|
+
documentActorId: object.id.href
|
|
224
|
+
});
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
213
227
|
return object;
|
|
214
228
|
}
|
|
215
229
|
/**
|
|
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
5
5
|
import { FetchError, UrlError } from "@fedify/vocab-runtime";
|
|
6
6
|
//#region src/federation/keycache.ts
|
|
7
|
-
const KEY_CACHE_GENERATION = "
|
|
7
|
+
const KEY_CACHE_GENERATION = "3";
|
|
8
8
|
var KvKeyCache = class {
|
|
9
9
|
kv;
|
|
10
10
|
prefix;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
require("./chunk-DDcVe30Y.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-CRiVlZd5.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
6
|
let es_toolkit = require("es-toolkit");
|
|
7
7
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
8
|
+
let _fedify_vocab_runtime_internal = require("@fedify/vocab-runtime/internal");
|
|
8
9
|
//#region src/utils/docloader.ts
|
|
9
10
|
const logger$1 = (0, _logtape_logtape.getLogger)([
|
|
10
11
|
"fedify",
|
|
@@ -25,12 +26,34 @@ const DEFAULT_MAX_REDIRECTION = 20;
|
|
|
25
26
|
* @throws {TypeError} If the key is invalid or unsupported.
|
|
26
27
|
* @since 0.4.0
|
|
27
28
|
*/
|
|
28
|
-
function getAuthenticatedDocumentLoader(identity,
|
|
29
|
+
function getAuthenticatedDocumentLoader(identity, options = {}) {
|
|
30
|
+
return createAuthenticatedDocumentLoader(identity, options);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The placeholder that the alternate document callback of
|
|
34
|
+
* `getRemoteDocument()` returns, so that the alternate document is loaded
|
|
35
|
+
* after the current hop is closed.
|
|
36
|
+
*/
|
|
37
|
+
const ALTERNATE_PLACEHOLDER = Object.freeze({
|
|
38
|
+
contextUrl: null,
|
|
39
|
+
document: null,
|
|
40
|
+
documentUrl: "about:blank"
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* The implementation of {@link getAuthenticatedDocumentLoader}. This is not
|
|
44
|
+
* exported from any entry point.
|
|
45
|
+
* @param identity The identity to get the document loader for.
|
|
46
|
+
* @param options The options for the document loader.
|
|
47
|
+
* @param deps The dependencies, for tests.
|
|
48
|
+
* @returns The authenticated document loader.
|
|
49
|
+
* @throws {TypeError} If the key is invalid or unsupported.
|
|
50
|
+
*/
|
|
51
|
+
function createAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAgent, specDeterminer, tracerProvider } = {}, deps = {}) {
|
|
29
52
|
require_http.validateCryptoKey(identity.privateKey);
|
|
30
53
|
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
31
54
|
options?.signal?.throwIfAborted();
|
|
32
55
|
let currentUrl = new URL(url).href;
|
|
33
|
-
|
|
56
|
+
validateUrl(currentUrl);
|
|
34
57
|
visited.add(currentUrl);
|
|
35
58
|
const originalRequest = (0, _fedify_vocab_runtime.createActivityPubRequest)(currentUrl, { userAgent });
|
|
36
59
|
function follow(nextUrl) {
|
|
@@ -39,28 +62,43 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAge
|
|
|
39
62
|
if (visited.has(nextUrl)) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
|
|
40
63
|
redirected++;
|
|
41
64
|
}
|
|
42
|
-
const
|
|
65
|
+
const result = await require_http.doubleKnockScoped(originalRequest, identity, {
|
|
43
66
|
specDeterminer,
|
|
44
67
|
log: (0, es_toolkit.curry)(_fedify_vocab_runtime.logRequest)(logger$1),
|
|
45
68
|
tracerProvider,
|
|
46
69
|
signal: options?.signal,
|
|
47
|
-
validateRedirect:
|
|
70
|
+
validateRedirect: (nextUrl) => {
|
|
48
71
|
follow(nextUrl);
|
|
49
|
-
|
|
72
|
+
validateUrl(nextUrl);
|
|
50
73
|
visited.add(nextUrl);
|
|
51
74
|
currentUrl = nextUrl;
|
|
52
75
|
}
|
|
76
|
+
}, (hopUrl) => openHop(hopUrl, options?.signal), async (response) => {
|
|
77
|
+
let alternateUrl;
|
|
78
|
+
const document = await (0, _fedify_vocab_runtime.getRemoteDocument)(currentUrl, response, (url) => {
|
|
79
|
+
alternateUrl = url;
|
|
80
|
+
return Promise.resolve(ALTERNATE_PLACEHOLDER);
|
|
81
|
+
});
|
|
82
|
+
if (alternateUrl == null) return { document };
|
|
83
|
+
if (!response.bodyUsed && response.body?.locked !== true) response.body?.cancel().catch((error) => {
|
|
84
|
+
logger$1.debug("Failed to cancel the response body from {url}: {error}", {
|
|
85
|
+
url: currentUrl,
|
|
86
|
+
error
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
return { alternateUrl };
|
|
53
90
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
91
|
+
if ("document" in result) return result.document;
|
|
92
|
+
follow(result.alternateUrl);
|
|
93
|
+
return load(result.alternateUrl, options, redirected, visited);
|
|
58
94
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
95
|
+
function openUnvalidatedHop(url, signal) {
|
|
96
|
+
if (deps.openHop != null) return deps.openHop(url);
|
|
97
|
+
return allowPrivateAddress ? (0, _fedify_vocab_runtime_internal.openDirectFetch)(url) : (0, _fedify_vocab_runtime_internal.openPublicFetch)(url, { signal });
|
|
98
|
+
}
|
|
99
|
+
async function openHop(url, signal) {
|
|
100
|
+
try {
|
|
101
|
+
return await openUnvalidatedHop(url, signal);
|
|
64
102
|
} catch (error) {
|
|
65
103
|
if (error instanceof _fedify_vocab_runtime.UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
|
|
66
104
|
url,
|
|
@@ -73,6 +111,12 @@ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, userAge
|
|
|
73
111
|
throw error;
|
|
74
112
|
}
|
|
75
113
|
}
|
|
114
|
+
function validateUrl(url) {
|
|
115
|
+
const parsedUrl = new URL(url);
|
|
116
|
+
const protocol = parsedUrl.protocol;
|
|
117
|
+
if (protocol !== "http:" && protocol !== "https:") throw new _fedify_vocab_runtime.UrlError(`Unsupported protocol: ${protocol}`);
|
|
118
|
+
if (parsedUrl.username !== "" || parsedUrl.password !== "") throw new _fedify_vocab_runtime.UrlError("URLs with credentials are not allowed");
|
|
119
|
+
}
|
|
76
120
|
return (url, options) => load(url, options);
|
|
77
121
|
}
|
|
78
122
|
//#endregion
|