@fedify/fedify 2.2.19 → 2.2.20
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-UmydCxtK.mjs → builder-BolQ56ab.mjs} +2 -2
- package/dist/{client-CAM_bQXx.d.cts → client-BIBzqEIp.d.cts} +0 -1
- package/dist/{client-9F_u23dc.mjs → client-DhxaVgOv.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-tJbL_Hkw.d.cts → context-CFQGaq5x.d.cts} +28 -2
- package/dist/{context-OkwEpc3-.d.ts → context-DdbLVV_k.d.ts} +28 -2
- package/dist/{deno-CD-8UlUi.mjs → deno-BUj4roJc.mjs} +1 -1
- package/dist/docloader-CbtF_0cD.mjs +125 -0
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +389 -28
- 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-DJK2udgX.js → http-C5g_VrA_.js} +172 -103
- package/dist/{http-BBBUbwKh.mjs → http-CQMVisM0.mjs} +195 -97
- package/dist/{http-DrUG4fPS.cjs → http-D8I7LBfx.cjs} +173 -104
- package/dist/{http-DqZhyEso.d.cts → http-SbfezERn.d.ts} +1 -2
- package/dist/{inbox-Dca_0fwc.mjs → inbox-CD8IDILN.mjs} +1 -1
- package/dist/{key-DolXUaLH.mjs → key-C3o3DGap.mjs} +1 -1
- package/dist/{kv-cache-DDm80t1K.js → kv-cache-CSKXXleR.js} +60 -18
- package/dist/{kv-cache-Bk3V1Tiu.cjs → kv-cache-DC9LcfoC.cjs} +59 -17
- package/dist/{ld-rTdIfZ63.mjs → ld-ijSr-0AB.mjs} +2 -2
- package/dist/{middleware-CpXoRHs3.mjs → middleware-CYAaUTi5.mjs} +78 -38
- package/dist/{middleware-BWOwEGpy.cjs → middleware-DNueR0GD.cjs} +184 -59
- package/dist/{middleware-B4-Rzdi3.cjs → middleware-DmcY2TWS.cjs} +1 -1
- package/dist/{middleware-BimXKBcP.js → middleware-sjhz30sm.js} +184 -59
- package/dist/{middleware-DYK_res8.mjs → middleware-yqqfI7oJ.mjs} +1 -1
- package/dist/{mod-B6LmugJ4.d.ts → mod-BRnwh2oy.d.ts} +2 -8
- package/dist/{mod-D2JyWtW3.d.cts → mod-BYWp77-z.d.cts} +1 -1
- package/dist/{mod-DQGjRJUG.d.ts → mod-BuRRdsfD.d.ts} +1 -1
- package/dist/{mod-ej_rLx8h.d.ts → mod-CM5_J_cx.d.ts} +1 -1
- package/dist/{mod-a6kmB8xD.d.cts → mod-CVSKw-5H.d.cts} +1 -1
- package/dist/{mod-V2vDnYNu.d.cts → mod-Co7LEK9X.d.cts} +2 -8
- 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-CVfzyM5V.mjs → owner-B8kXzD0q.mjs} +2 -2
- package/dist/{proof-gOGM-sJS.mjs → proof-CBQOjOgA.mjs} +2 -2
- package/dist/{proof-CaQ4KG6y.js → proof-DKX6nASL.js} +1 -1
- package/dist/{proof-DhaTTn-P.cjs → proof-qiuGJCD7.cjs} +1 -1
- package/dist/{send-D35Va72B.mjs → send-DRpisX_f.mjs} +117 -32
- package/dist/sig/http.test.mjs +528 -2
- package/dist/sig/key.test.mjs +2 -2
- package/dist/sig/ld.test.mjs +2 -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 +1 -1
- package/dist/sig/proof.test.mjs +1 -1
- package/dist/{temporal-CA4Y9hBJ.mjs → temporal-BQGEINad.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 +318 -34
- 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 +6 -6
- package/dist/docloader-D1ghurDJ.mjs +0 -83
|
@@ -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.2.
|
|
13
|
+
var version = "2.2.20";
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/sig/accept.ts
|
|
16
16
|
/**
|
|
@@ -1597,10 +1597,10 @@ async function createRedirectRequest(request, location, body, validateRedirect)
|
|
|
1597
1597
|
cache: request.cache
|
|
1598
1598
|
});
|
|
1599
1599
|
}
|
|
1600
|
-
async function fetchDoubleKnockRequest(request, signedRequest, signal) {
|
|
1600
|
+
async function fetchDoubleKnockRequest(session, request, signedRequest, signal) {
|
|
1601
1601
|
const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
|
|
1602
1602
|
for (let attempt = 1;; attempt++) try {
|
|
1603
|
-
return await fetch(signedRequest, {
|
|
1603
|
+
return await session.fetch(signedRequest, {
|
|
1604
1604
|
redirect: "manual",
|
|
1605
1605
|
signal
|
|
1606
1606
|
});
|
|
@@ -1651,118 +1651,187 @@ function getAbortReason(signal) {
|
|
|
1651
1651
|
return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
|
|
1652
1652
|
}
|
|
1653
1653
|
/**
|
|
1654
|
-
* Performs a double-knock request
|
|
1655
|
-
*
|
|
1656
|
-
*
|
|
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.
|
|
1657
1659
|
* @param request The request to send.
|
|
1658
1660
|
* @param identity The identity to use for signing the request.
|
|
1659
1661
|
* @param options The options for double-knock requests.
|
|
1660
|
-
* @
|
|
1661
|
-
*
|
|
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`.
|
|
1662
1666
|
*/
|
|
1663
|
-
async function
|
|
1664
|
-
|
|
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
|
+
}
|
|
1665
1684
|
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
1680
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1681
|
-
if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
1682
|
-
const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
|
|
1683
|
-
if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
|
|
1684
|
-
return doubleKnockInternal(redirectRequest, identity, {
|
|
1685
|
-
...options,
|
|
1686
|
-
body
|
|
1687
|
-
}, redirected + 1, visited);
|
|
1688
|
-
} else if (response.status === 400 || response.status === 401 || response.status > 401) {
|
|
1689
|
-
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([
|
|
1690
1698
|
"fedify",
|
|
1691
1699
|
"sig",
|
|
1692
1700
|
"http"
|
|
1693
|
-
])
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
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 maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
1710
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1711
|
+
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
1712
|
+
let session = await openHop(request.url);
|
|
1713
|
+
try {
|
|
1714
|
+
for (let redirected = 0;; redirected++) {
|
|
1715
|
+
visited.add(request.url);
|
|
1716
|
+
const origin = new URL(request.url).origin;
|
|
1717
|
+
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
1718
|
+
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1719
|
+
spec: firstTrySpec,
|
|
1720
|
+
tracerProvider,
|
|
1721
|
+
body
|
|
1722
|
+
});
|
|
1723
|
+
log?.(signedRequest);
|
|
1724
|
+
let response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
|
|
1725
|
+
const firstRedirected = isRedirectResponse(response);
|
|
1726
|
+
if (!firstRedirected && (response.status === 400 || response.status === 401 || response.status > 401)) {
|
|
1727
|
+
const logger = getLogger([
|
|
1728
|
+
"fedify",
|
|
1729
|
+
"sig",
|
|
1730
|
+
"http"
|
|
1731
|
+
]);
|
|
1732
|
+
const acceptSigHeader = response.headers.get("Accept-Signature");
|
|
1733
|
+
if (acceptSigHeader != null) {
|
|
1734
|
+
const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
|
|
1735
|
+
const localKeyId = identity.keyId.href;
|
|
1736
|
+
const localAlg = "rsa-v1_5-sha256";
|
|
1737
|
+
let fulfilled = false;
|
|
1738
|
+
let challengeRequest;
|
|
1739
|
+
for (const entry of entries) {
|
|
1740
|
+
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
1741
|
+
if (rfc9421 == null) continue;
|
|
1742
|
+
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
1743
|
+
label: rfc9421.label,
|
|
1744
|
+
components: rfc9421.components
|
|
1745
|
+
});
|
|
1746
|
+
try {
|
|
1747
|
+
challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
|
|
1748
|
+
spec: "rfc9421",
|
|
1749
|
+
tracerProvider,
|
|
1750
|
+
body,
|
|
1751
|
+
rfc9421
|
|
1752
|
+
});
|
|
1753
|
+
fulfilled = true;
|
|
1754
|
+
} catch (error) {
|
|
1755
|
+
logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
|
|
1756
|
+
label: entry.label,
|
|
1757
|
+
error
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
if (fulfilled && challengeRequest != null) {
|
|
1762
|
+
cancelResponseBody(response, request.url);
|
|
1763
|
+
signedRequest = challengeRequest;
|
|
1764
|
+
log?.(signedRequest);
|
|
1765
|
+
response = await session.fetch(signedRequest, {
|
|
1766
|
+
redirect: "manual",
|
|
1767
|
+
signal
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
if (fulfilled && !isRedirectResponse(response)) {
|
|
1771
|
+
if (response.status < 300) {
|
|
1772
|
+
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
1773
|
+
return {
|
|
1774
|
+
response,
|
|
1775
|
+
session
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
if (response.status !== 400 && response.status !== 401) return {
|
|
1779
|
+
response,
|
|
1780
|
+
session
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
if (!isRedirectResponse(response)) {
|
|
1785
|
+
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
1786
|
+
getLogger([
|
|
1787
|
+
"fedify",
|
|
1788
|
+
"sig",
|
|
1789
|
+
"http"
|
|
1790
|
+
]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
1791
|
+
spec: firstTrySpec,
|
|
1792
|
+
secondSpec: spec,
|
|
1793
|
+
status: response.status,
|
|
1794
|
+
statusText: response.statusText
|
|
1714
1795
|
});
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1796
|
+
cancelResponseBody(response, request.url);
|
|
1797
|
+
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1798
|
+
spec,
|
|
1799
|
+
tracerProvider,
|
|
1800
|
+
body
|
|
1720
1801
|
});
|
|
1802
|
+
log?.(signedRequest);
|
|
1803
|
+
response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
|
|
1804
|
+
if (!isRedirectResponse(response) && response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
|
|
1721
1805
|
}
|
|
1722
|
-
}
|
|
1723
|
-
if (
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
});
|
|
1730
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
|
|
1731
|
-
...options,
|
|
1732
|
-
body
|
|
1733
|
-
});
|
|
1734
|
-
}
|
|
1735
|
-
if (fulfilled && response.status < 300) {
|
|
1736
|
-
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
1737
|
-
return response;
|
|
1738
|
-
}
|
|
1739
|
-
if (fulfilled && response.status !== 400 && response.status !== 401) return response;
|
|
1740
|
-
}
|
|
1741
|
-
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
1742
|
-
logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
1743
|
-
spec: firstTrySpec,
|
|
1744
|
-
secondSpec: spec,
|
|
1745
|
-
status: response.status,
|
|
1746
|
-
statusText: response.statusText
|
|
1747
|
-
});
|
|
1748
|
-
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1749
|
-
spec,
|
|
1750
|
-
tracerProvider,
|
|
1751
|
-
body
|
|
1752
|
-
});
|
|
1753
|
-
log?.(signedRequest);
|
|
1754
|
-
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
1755
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1806
|
+
} else if (!firstRedirected) await specDeterminer?.rememberSpec(origin, firstTrySpec);
|
|
1807
|
+
if (!isRedirectResponse(response)) return {
|
|
1808
|
+
response,
|
|
1809
|
+
session
|
|
1810
|
+
};
|
|
1811
|
+
cancelResponseBody(response, request.url);
|
|
1812
|
+
await session.close();
|
|
1756
1813
|
if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
1757
|
-
const
|
|
1814
|
+
const location = response.headers.get("Location");
|
|
1815
|
+
const redirectRequest = await createRedirectRequest(request, location, body, options.validateRedirect);
|
|
1758
1816
|
if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1817
|
+
request = redirectRequest;
|
|
1818
|
+
session = await openHop(request.url);
|
|
1819
|
+
}
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
try {
|
|
1822
|
+
await session.close();
|
|
1823
|
+
} catch (closeError) {
|
|
1824
|
+
getLogger([
|
|
1825
|
+
"fedify",
|
|
1826
|
+
"sig",
|
|
1827
|
+
"http"
|
|
1828
|
+
]).debug("Failed to close the session for {url}: {error}", {
|
|
1829
|
+
url: request.url,
|
|
1830
|
+
error: closeError
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
throw error;
|
|
1834
|
+
}
|
|
1766
1835
|
}
|
|
1767
1836
|
/**
|
|
1768
1837
|
* Performs a timing-safe equality comparison between two `Uint8Array` values.
|
|
@@ -1792,4 +1861,4 @@ function timingSafeEqual(a, b) {
|
|
|
1792
1861
|
return result === 0;
|
|
1793
1862
|
}
|
|
1794
1863
|
//#endregion
|
|
1795
|
-
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,
|
|
1864
|
+
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,9 +1,9 @@
|
|
|
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-BUj4roJc.mjs";
|
|
5
5
|
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-Dd__NiUL.mjs";
|
|
6
|
-
import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-
|
|
6
|
+
import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-C3o3DGap.mjs";
|
|
7
7
|
import { CryptographicKey } from "@fedify/vocab";
|
|
8
8
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
9
9
|
import { FetchError } from "@fedify/vocab-runtime";
|
|
@@ -849,10 +849,10 @@ async function createRedirectRequest(request, location, body, validateRedirect)
|
|
|
849
849
|
cache: request.cache
|
|
850
850
|
});
|
|
851
851
|
}
|
|
852
|
-
async function fetchDoubleKnockRequest(request, signedRequest, signal) {
|
|
852
|
+
async function fetchDoubleKnockRequest(session, request, signedRequest, signal) {
|
|
853
853
|
const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
|
|
854
854
|
for (let attempt = 1;; attempt++) try {
|
|
855
|
-
return await fetch(signedRequest, {
|
|
855
|
+
return await session.fetch(signedRequest, {
|
|
856
856
|
redirect: "manual",
|
|
857
857
|
signal
|
|
858
858
|
});
|
|
@@ -913,108 +913,206 @@ function getAbortReason(signal) {
|
|
|
913
913
|
* @since 1.6.0
|
|
914
914
|
*/
|
|
915
915
|
async function doubleKnock(request, identity, options = {}) {
|
|
916
|
-
|
|
916
|
+
const { response } = await doubleKnockCore(request, identity, options, openGlobalFetchHop);
|
|
917
|
+
return response;
|
|
917
918
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
919
|
+
/**
|
|
920
|
+
* Performs a double-knock request like {@link doubleKnock}, opening a session
|
|
921
|
+
* for every hop with the given function, and passes the final response to
|
|
922
|
+
* the given function. The final session is closed after `consume` settles,
|
|
923
|
+
* so `consume` must read or cancel the response body before it returns.
|
|
924
|
+
* This is not exported from any entry point.
|
|
925
|
+
* @param request The request to send.
|
|
926
|
+
* @param identity The identity to use for signing the request.
|
|
927
|
+
* @param options The options for double-knock requests.
|
|
928
|
+
* @param openHop The function to open a session for each hop. The session of
|
|
929
|
+
* a hop is closed before the next hop is opened.
|
|
930
|
+
* @param consume The function to consume the final response.
|
|
931
|
+
* @returns The value returned by `consume`.
|
|
932
|
+
*/
|
|
933
|
+
async function doubleKnockScoped(request, identity, options, openHop, consume) {
|
|
934
|
+
const { response, session } = await doubleKnockCore(request, identity, options, openHop);
|
|
935
|
+
try {
|
|
936
|
+
return await consume(response);
|
|
937
|
+
} catch (error) {
|
|
938
|
+
getLogger([
|
|
939
|
+
"fedify",
|
|
940
|
+
"sig",
|
|
941
|
+
"http"
|
|
942
|
+
]).debug("Failed to consume the response from {url}; closing the session: {error}", {
|
|
943
|
+
url: request.url,
|
|
944
|
+
error
|
|
945
|
+
});
|
|
946
|
+
throw error;
|
|
947
|
+
} finally {
|
|
948
|
+
await session.close();
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* A hop session for {@link doubleKnock}: it uses the global `fetch()` without
|
|
953
|
+
* validating or pinning addresses, and its `close()` does nothing, so that
|
|
954
|
+
* the final response can be returned to the caller.
|
|
955
|
+
*/
|
|
956
|
+
function openGlobalFetchHop(url) {
|
|
957
|
+
const sessionUrl = new URL(url);
|
|
958
|
+
return Promise.resolve({
|
|
959
|
+
get url() {
|
|
960
|
+
return new URL(sessionUrl);
|
|
961
|
+
},
|
|
962
|
+
fetch: (input, init) => fetch(input, init),
|
|
963
|
+
close: () => Promise.resolve()
|
|
929
964
|
});
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
965
|
+
}
|
|
966
|
+
function isRedirectResponse(response) {
|
|
967
|
+
return response.status >= 300 && response.status < 400 && response.headers.has("Location");
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Starts cancelling the body of a response that is not passed to the caller,
|
|
971
|
+
* without waiting for it to settle. The cancellation of a branch of a tee
|
|
972
|
+
* (e.g., a body cloned by a fetch mock) settles only after the other branch
|
|
973
|
+
* is cancelled too, per the Streams standard, so awaiting it could block
|
|
974
|
+
* forever. Failures are only logged.
|
|
975
|
+
*/
|
|
976
|
+
function cancelResponseBody(response, url) {
|
|
977
|
+
response.body?.cancel().catch((error) => {
|
|
978
|
+
getLogger([
|
|
942
979
|
"fedify",
|
|
943
980
|
"sig",
|
|
944
981
|
"http"
|
|
945
|
-
])
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
982
|
+
]).debug("Failed to cancel the response body from {url}: {error}", {
|
|
983
|
+
url,
|
|
984
|
+
error
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
async function doubleKnockCore(request, identity, options, openHop) {
|
|
989
|
+
const { specDeterminer, log, tracerProvider, signal } = options;
|
|
990
|
+
const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
|
|
991
|
+
const visited = /* @__PURE__ */ new Set();
|
|
992
|
+
const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
|
|
993
|
+
let session = await openHop(request.url);
|
|
994
|
+
try {
|
|
995
|
+
for (let redirected = 0;; redirected++) {
|
|
996
|
+
visited.add(request.url);
|
|
997
|
+
const origin = new URL(request.url).origin;
|
|
998
|
+
const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
|
|
999
|
+
let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1000
|
+
spec: firstTrySpec,
|
|
1001
|
+
tracerProvider,
|
|
1002
|
+
body
|
|
1003
|
+
});
|
|
1004
|
+
log?.(signedRequest);
|
|
1005
|
+
let response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
|
|
1006
|
+
const firstRedirected = isRedirectResponse(response);
|
|
1007
|
+
if (!firstRedirected && (response.status === 400 || response.status === 401 || response.status > 401)) {
|
|
1008
|
+
const logger = getLogger([
|
|
1009
|
+
"fedify",
|
|
1010
|
+
"sig",
|
|
1011
|
+
"http"
|
|
1012
|
+
]);
|
|
1013
|
+
const acceptSigHeader = response.headers.get("Accept-Signature");
|
|
1014
|
+
if (acceptSigHeader != null) {
|
|
1015
|
+
const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
|
|
1016
|
+
const localKeyId = identity.keyId.href;
|
|
1017
|
+
const localAlg = "rsa-v1_5-sha256";
|
|
1018
|
+
let fulfilled = false;
|
|
1019
|
+
let challengeRequest;
|
|
1020
|
+
for (const entry of entries) {
|
|
1021
|
+
const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
|
|
1022
|
+
if (rfc9421 == null) continue;
|
|
1023
|
+
logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
|
|
1024
|
+
label: rfc9421.label,
|
|
1025
|
+
components: rfc9421.components
|
|
1026
|
+
});
|
|
1027
|
+
try {
|
|
1028
|
+
challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
|
|
1029
|
+
spec: "rfc9421",
|
|
1030
|
+
tracerProvider,
|
|
1031
|
+
body,
|
|
1032
|
+
rfc9421
|
|
1033
|
+
});
|
|
1034
|
+
fulfilled = true;
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
|
|
1037
|
+
label: entry.label,
|
|
1038
|
+
error
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
if (fulfilled && challengeRequest != null) {
|
|
1043
|
+
cancelResponseBody(response, request.url);
|
|
1044
|
+
signedRequest = challengeRequest;
|
|
1045
|
+
log?.(signedRequest);
|
|
1046
|
+
response = await session.fetch(signedRequest, {
|
|
1047
|
+
redirect: "manual",
|
|
1048
|
+
signal
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
if (fulfilled && !isRedirectResponse(response)) {
|
|
1052
|
+
if (response.status < 300) {
|
|
1053
|
+
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
1054
|
+
return {
|
|
1055
|
+
response,
|
|
1056
|
+
session
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
if (response.status !== 400 && response.status !== 401) return {
|
|
1060
|
+
response,
|
|
1061
|
+
session
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (!isRedirectResponse(response)) {
|
|
1066
|
+
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
1067
|
+
getLogger([
|
|
1068
|
+
"fedify",
|
|
1069
|
+
"sig",
|
|
1070
|
+
"http"
|
|
1071
|
+
]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
1072
|
+
spec: firstTrySpec,
|
|
1073
|
+
secondSpec: spec,
|
|
1074
|
+
status: response.status,
|
|
1075
|
+
statusText: response.statusText
|
|
966
1076
|
});
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1077
|
+
cancelResponseBody(response, request.url);
|
|
1078
|
+
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1079
|
+
spec,
|
|
1080
|
+
tracerProvider,
|
|
1081
|
+
body
|
|
972
1082
|
});
|
|
1083
|
+
log?.(signedRequest);
|
|
1084
|
+
response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
|
|
1085
|
+
if (!isRedirectResponse(response) && response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
|
|
973
1086
|
}
|
|
974
|
-
}
|
|
975
|
-
if (
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
});
|
|
982
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
|
|
983
|
-
...options,
|
|
984
|
-
body
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
if (fulfilled && response.status < 300) {
|
|
988
|
-
await specDeterminer?.rememberSpec(origin, "rfc9421");
|
|
989
|
-
return response;
|
|
990
|
-
}
|
|
991
|
-
if (fulfilled && response.status !== 400 && response.status !== 401) return response;
|
|
992
|
-
}
|
|
993
|
-
const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
|
|
994
|
-
logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
|
|
995
|
-
spec: firstTrySpec,
|
|
996
|
-
secondSpec: spec,
|
|
997
|
-
status: response.status,
|
|
998
|
-
statusText: response.statusText
|
|
999
|
-
});
|
|
1000
|
-
signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
|
|
1001
|
-
spec,
|
|
1002
|
-
tracerProvider,
|
|
1003
|
-
body
|
|
1004
|
-
});
|
|
1005
|
-
log?.(signedRequest);
|
|
1006
|
-
response = await fetchDoubleKnockRequest(request, signedRequest, signal);
|
|
1007
|
-
if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
|
|
1087
|
+
} else if (!firstRedirected) await specDeterminer?.rememberSpec(origin, firstTrySpec);
|
|
1088
|
+
if (!isRedirectResponse(response)) return {
|
|
1089
|
+
response,
|
|
1090
|
+
session
|
|
1091
|
+
};
|
|
1092
|
+
cancelResponseBody(response, request.url);
|
|
1093
|
+
await session.close();
|
|
1008
1094
|
if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
|
|
1009
|
-
const
|
|
1095
|
+
const location = response.headers.get("Location");
|
|
1096
|
+
const redirectRequest = await createRedirectRequest(request, location, body, options.validateRedirect);
|
|
1010
1097
|
if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1098
|
+
request = redirectRequest;
|
|
1099
|
+
session = await openHop(request.url);
|
|
1100
|
+
}
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
try {
|
|
1103
|
+
await session.close();
|
|
1104
|
+
} catch (closeError) {
|
|
1105
|
+
getLogger([
|
|
1106
|
+
"fedify",
|
|
1107
|
+
"sig",
|
|
1108
|
+
"http"
|
|
1109
|
+
]).debug("Failed to close the session for {url}: {error}", {
|
|
1110
|
+
url: request.url,
|
|
1111
|
+
error: closeError
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
throw error;
|
|
1115
|
+
}
|
|
1018
1116
|
}
|
|
1019
1117
|
/**
|
|
1020
1118
|
* Performs a timing-safe equality comparison between two `Uint8Array` values.
|
|
@@ -1044,4 +1142,4 @@ function timingSafeEqual(a, b) {
|
|
|
1044
1142
|
return result === 0;
|
|
1045
1143
|
}
|
|
1046
1144
|
//#endregion
|
|
1047
|
-
export {
|
|
1145
|
+
export { formatRfc9421SignatureParameters as a, signRequest as c, verifyRequestDetailed as d, formatRfc9421Signature as i, timingSafeEqual as l, doubleKnock as n, parseRfc9421Signature as o, doubleKnockScoped as r, parseRfc9421SignatureInput as s, createRfc9421SignatureBase as t, verifyRequest as u };
|