@fedify/fedify 2.4.0-pr.936.41 → 2.5.0-dev.2271
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/README.md +50 -39
- package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
- package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
- package/dist/assert_exists-cffSpJ7X.mjs +28 -0
- package/dist/assert_false-CPdRnc3E.mjs +23 -0
- package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
- package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
- package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
- package/dist/assert_rejects-CTptdYck.mjs +39 -0
- package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
- package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
- package/dist/assertion_error-B-cx0fFA.mjs +31 -0
- package/dist/audience-CHQ-FlLD.mjs +95 -0
- package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
- package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
- package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
- package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
- package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
- package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
- package/dist/compat/mod.cjs +1 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/mod.js +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +71 -3
- package/dist/compat/public-audience.test.mjs +14 -5
- package/dist/compat/secured-document.test.d.mts +2 -0
- package/dist/compat/secured-document.test.mjs +58 -0
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/compound-proof-B-9mXxFW.mjs +989 -0
- package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
- package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
- package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
- package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
- package/dist/dist-DJQM4u7i.mjs +189 -0
- package/dist/docloader-DBoYbvYW.mjs +87 -0
- package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
- package/dist/federation/audience.test.d.mts +2 -0
- package/dist/federation/audience.test.mjs +130 -0
- package/dist/federation/builder.test.mjs +54 -5
- package/dist/federation/circuit-breaker.test.mjs +838 -7
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/compound-fanout.test.d.mts +2 -0
- package/dist/federation/compound-fanout.test.mjs +228 -0
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +384 -0
- package/dist/federation/handler.test.mjs +385 -15
- package/dist/federation/hashlink-media.test.d.mts +2 -0
- package/dist/federation/hashlink-media.test.mjs +422 -0
- package/dist/federation/idempotency.test.mjs +6 -6
- package/dist/federation/inbox-report.test.d.mts +2 -0
- package/dist/federation/inbox-report.test.mjs +475 -0
- package/dist/federation/inbox.test.mjs +3 -3
- package/dist/federation/keycache.test.mjs +215 -14
- package/dist/federation/kv.test.mjs +3 -3
- package/dist/federation/metrics.test.mjs +5 -5
- package/dist/federation/middleware.test.mjs +967 -35
- package/dist/federation/mod.cjs +18 -261
- package/dist/federation/mod.d.cts +5 -5
- package/dist/federation/mod.d.ts +5 -5
- package/dist/federation/mod.js +279 -1
- package/dist/federation/mq.test.mjs +123 -47
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/outgoing-proof.test.d.mts +2 -0
- package/dist/federation/outgoing-proof.test.mjs +931 -0
- package/dist/federation/portable-actor.test.d.mts +2 -0
- package/dist/federation/portable-actor.test.mjs +496 -0
- package/dist/federation/portable-chain.test.d.mts +2 -0
- package/dist/federation/portable-chain.test.mjs +370 -0
- package/dist/federation/portable-collection.test.d.mts +2 -0
- package/dist/federation/portable-collection.test.mjs +832 -0
- package/dist/federation/portable-delivery.test.d.mts +2 -0
- package/dist/federation/portable-delivery.test.mjs +746 -0
- package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
- package/dist/federation/portable-gateway-keys.test.mjs +686 -0
- package/dist/federation/portable-inbox.test.d.mts +2 -0
- package/dist/federation/portable-inbox.test.mjs +1535 -0
- package/dist/federation/portable-parse-uri.test.d.mts +2 -0
- package/dist/federation/portable-parse-uri.test.mjs +348 -0
- package/dist/federation/portable-route-activity.test.d.mts +2 -0
- package/dist/federation/portable-route-activity.test.mjs +259 -0
- package/dist/federation/portable.test.d.mts +2 -0
- package/dist/federation/portable.test.mjs +1072 -0
- package/dist/federation/retry.test.mjs +4 -3
- package/dist/federation/router.test.mjs +9 -159
- package/dist/federation/send.test.mjs +245 -27
- package/dist/federation/tasks/codec.test.mjs +4 -4
- package/dist/federation/tasks/enqueue.test.mjs +42 -4
- package/dist/federation/tasks/tasks.test.mjs +7 -6
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +275 -12
- package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
- package/dist/http-B5JT115n.cjs +10630 -0
- package/dist/http-DydWCB7R.js +6527 -0
- package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
- package/dist/key-BspPiXqn.mjs +951 -0
- package/dist/key-C1nv7_jD.d.ts +1125 -0
- package/dist/key-DYxuyd-s.d.cts +1125 -0
- package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
- package/dist/keycache-Da8A8mZa.mjs +200 -0
- package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
- package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
- package/dist/kv-DBRWVx3Q.mjs +104 -0
- package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
- package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
- package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
- package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
- package/dist/metrics-DVKYY6K1.d.cts +36 -0
- package/dist/metrics-DoRtI0qU.d.ts +36 -0
- package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
- package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
- package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
- package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
- package/dist/mod-CavhAgLw.d.cts +320 -0
- package/dist/mod-CtpF2BU5.d.ts +320 -0
- package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
- package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
- package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
- package/dist/mod.cjs +40 -39
- package/dist/mod.d.cts +10 -11
- package/dist/mod.d.ts +10 -11
- package/dist/mod.js +9 -9
- package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
- package/dist/nodeinfo/client.test.mjs +139 -4
- package/dist/nodeinfo/handler.test.mjs +5 -5
- 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/nodeinfo/types.test.mjs +4 -4
- package/dist/otel/exporter.test.mjs +3 -3
- package/dist/otel/mod.cjs +0 -1
- package/dist/otel/mod.d.cts +1 -1
- package/dist/otel/mod.d.ts +1 -1
- package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
- package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
- package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
- package/dist/owner-DYgfTFHy.mjs +267 -0
- package/dist/portable-collection-BpsVsRov.js +670 -0
- package/dist/portable-collection-C74P1vtC.mjs +418 -0
- package/dist/portable-collection-DZd2bm7M.cjs +686 -0
- package/dist/portable-key-BIMLrAP4.mjs +549 -0
- package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
- package/dist/proof-Dxy6ByqI.mjs +1299 -0
- package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
- package/dist/router-u5O5KCQr.mjs +156 -0
- package/dist/runtime/mod.cjs +1 -1
- package/dist/runtime/mod.js +1 -1
- package/dist/secured-document-CxzZZ9nL.mjs +35 -0
- package/dist/send-C4dDpAxm.mjs +495 -0
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
- package/dist/sig/compound-proof-discovery.test.mjs +331 -0
- package/dist/sig/compound-proof-producer.test.d.mts +2 -0
- package/dist/sig/compound-proof-producer.test.mjs +107 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +833 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +411 -0
- package/dist/sig/http.test.mjs +702 -31
- package/dist/sig/key-owner-evidence.test.d.mts +2 -0
- package/dist/sig/key-owner-evidence.test.mjs +100 -0
- package/dist/sig/key.test.mjs +220 -8
- package/dist/sig/ld.test.mjs +65 -7
- package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
- package/dist/sig/mitra-receive-interop.test.mjs +260 -0
- package/dist/sig/mitra-send-interop.test.d.mts +2 -0
- package/dist/sig/mitra-send-interop.test.mjs +461 -0
- package/dist/sig/mod.cjs +18 -16
- package/dist/sig/mod.d.cts +4 -4
- package/dist/sig/mod.d.ts +4 -4
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +108 -9
- package/dist/sig/portable-collection.test.d.mts +2 -0
- package/dist/sig/portable-collection.test.mjs +697 -0
- package/dist/sig/portable-interop.test.d.mts +2 -0
- package/dist/sig/portable-interop.test.mjs +156 -0
- package/dist/sig/portable-key-id.test.d.mts +2 -0
- package/dist/sig/portable-key-id.test.mjs +67 -0
- package/dist/sig/portable-key.test.d.mts +2 -0
- package/dist/sig/portable-key.test.mjs +1439 -0
- package/dist/sig/proof.test.mjs +1753 -16
- package/dist/sig/tootik-request-interop.test.d.mts +2 -0
- package/dist/sig/tootik-request-interop.test.mjs +190 -0
- package/dist/sig/tootik-send-interop.test.d.mts +2 -0
- package/dist/sig/tootik-send-interop.test.mjs +331 -0
- package/dist/sig/verification.test.d.mts +2 -0
- package/dist/sig/verification.test.mjs +310 -0
- package/dist/std__assert-DvMDRPes.mjs +8 -0
- package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
- package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
- package/dist/testing/context.test.d.mts +2 -0
- package/dist/testing/context.test.mjs +19 -0
- package/dist/testing/kv.test.d.mts +2 -0
- package/dist/testing/kv.test.mjs +101 -0
- package/dist/testing/mod.d.mts +1617 -48
- package/dist/testing/mod.mjs +6 -3
- package/dist/testing-t8xDeJZ5.mjs +8 -0
- package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
- package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
- package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
- package/dist/utils/docloader.test.mjs +530 -8
- package/dist/utils/kv-cache.test.mjs +2 -2
- 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/dist/verification-C3954hQh.mjs +5 -0
- package/dist/verification-Dpfcw8ml.mjs +105 -0
- package/dist/vocab/cjs.test.mjs +1 -1
- package/package.json +8 -8
- package/dist/assert_rejects-EgstcQy5.mjs +0 -82
- package/dist/docloader-e1khNUKy.mjs +0 -49
- package/dist/http-CsmQ-BQ4.js +0 -2483
- package/dist/http-VyDTd4G3.d.cts +0 -553
- package/dist/http-hlIjmewS.cjs +0 -2694
- package/dist/http-lf8Hsd91.d.ts +0 -553
- package/dist/key-DfPsX8hv.mjs +0 -453
- package/dist/keycache-_DHqz5z_.mjs +0 -97
- package/dist/metrics-CoUJLQhZ.d.cts +0 -276
- package/dist/metrics-Dc4jR-IC.d.ts +0 -276
- package/dist/mod-B0hW12_O.d.cts +0 -307
- package/dist/mod-COIAjwRS.d.ts +0 -307
- package/dist/owner-Br9YYzn_.mjs +0 -120
- package/dist/proof-B4ngoaNQ.mjs +0 -316
- package/dist/proof-B6E0XUXJ.js +0 -1415
- package/dist/proof-NN4xCQGL.cjs +0 -1562
- package/dist/send-CxHKUNzv.mjs +0 -239
- package/dist/std__assert-B2NnY9bq.mjs +0 -79
- package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
- /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
- /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
- /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
- /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
- /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
- /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
- /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
- /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
- /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
- /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
- /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
- /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Temporal } from "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { a as getRawCanonicalPortableId, c as isPortableActorDocument, n as getCanonicalPortableId } from "./portable-key-id-Ca30-hOa.mjs";
|
|
5
|
+
import { CryptographicKey, Object as Object$1 } from "@fedify/vocab";
|
|
6
|
+
//#region src/sig/key-owner-evidence.ts
|
|
7
|
+
/**
|
|
8
|
+
* How long a key of an [FEP-ef61] portable actor is trusted after its actor's
|
|
9
|
+
* signed document vouched for it, however long the proof of the document is
|
|
10
|
+
* valid. The actor can drop the key or the gateway from its document at any
|
|
11
|
+
* time, so the key is looked up again at least this often.
|
|
12
|
+
*
|
|
13
|
+
* [FEP-ef61]: https://w3id.org/fep/ef61
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
const MAX_PORTABLE_KEY_TTL = Temporal.Duration.from({ hours: 1 });
|
|
17
|
+
const candidates = /* @__PURE__ */ new WeakMap();
|
|
18
|
+
const verified = /* @__PURE__ */ new WeakMap();
|
|
19
|
+
/**
|
|
20
|
+
* Attaches to a key that was just resolved the evidence of the portable actor
|
|
21
|
+
* that owns it. The evidence is not used until the key verifies
|
|
22
|
+
* a signature; see {@link promoteKeyOwnerEvidence}.
|
|
23
|
+
*
|
|
24
|
+
* Nothing is attached unless the document, the key's owner, and the actor
|
|
25
|
+
* the key ID belongs to agree on the actor's canonical portable ID.
|
|
26
|
+
* @param key The key, whose owner the document vouched for.
|
|
27
|
+
* @param document The expanded root node of the actor's verified document.
|
|
28
|
+
* @param expires When the evidence must not be used anymore.
|
|
29
|
+
* @returns Whether the evidence was attached.
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
function attachKeyOwnerEvidence(key, document, expires) {
|
|
33
|
+
if (!(key instanceof CryptographicKey) || key.id == null || key.ownerId == null || key.publicKey == null || document == null || typeof document !== "object" || Array.isArray(document)) return false;
|
|
34
|
+
const rawId = document["@id"];
|
|
35
|
+
if (typeof rawId !== "string") return false;
|
|
36
|
+
const keyBase = new URL(key.id.href);
|
|
37
|
+
keyBase.hash = "";
|
|
38
|
+
const ownerId = getCanonicalPortableId(key.ownerId);
|
|
39
|
+
if (ownerId == null || getRawCanonicalPortableId(rawId) !== ownerId || getCanonicalPortableId(keyBase) !== ownerId) return false;
|
|
40
|
+
candidates.set(key, {
|
|
41
|
+
keyId: key.id.href,
|
|
42
|
+
keyOwner: key.ownerId.href,
|
|
43
|
+
publicKey: key.publicKey,
|
|
44
|
+
ownerId,
|
|
45
|
+
document: structuredClone(document),
|
|
46
|
+
expires
|
|
47
|
+
});
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Makes the evidence attached to a key usable, as the key has just verified
|
|
52
|
+
* an HTTP Signature. Nothing happens if the key has no evidence.
|
|
53
|
+
* @param key The key that verified a signature.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
function promoteKeyOwnerEvidence(key) {
|
|
57
|
+
const evidence = candidates.get(key);
|
|
58
|
+
if (evidence == null) return;
|
|
59
|
+
candidates.delete(key);
|
|
60
|
+
verified.set(key, evidence);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Gets the evidence of the portable actor that owns a key HTTP Signature
|
|
64
|
+
* verification returned.
|
|
65
|
+
* @param key The key.
|
|
66
|
+
* @param now The current time.
|
|
67
|
+
* @returns The evidence, or `undefined` if the key was not returned by HTTP
|
|
68
|
+
* Signature verification, if it has no evidence, if the evidence
|
|
69
|
+
* expired, or if the key has been changed since.
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
function getVerifiedKeyOwnerEvidence(key, now = Temporal.Now.instant()) {
|
|
73
|
+
const evidence = verified.get(key);
|
|
74
|
+
if (evidence == null) return void 0;
|
|
75
|
+
if (Temporal.Instant.compare(now, evidence.expires) >= 0) {
|
|
76
|
+
verified.delete(key);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (key.id?.href !== evidence.keyId || key.ownerId?.href !== evidence.keyOwner || key.publicKey !== evidence.publicKey) return;
|
|
80
|
+
return evidence;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Parses the actor from the evidence, i.e., from the expansion of its
|
|
84
|
+
* document whose proof was verified. Every call returns a new object.
|
|
85
|
+
* @param evidence The evidence.
|
|
86
|
+
* @param options Options for parsing the document.
|
|
87
|
+
* @returns The actor, or `null` if the document is not the actor that
|
|
88
|
+
* the evidence is about, or the evidence expired meanwhile.
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
async function parseKeyOwnerEvidence(evidence, options = {}) {
|
|
92
|
+
const refuse = (url) => Promise.reject(/* @__PURE__ */ new Error(`Refusing to fetch ${url}.`));
|
|
93
|
+
let actor;
|
|
94
|
+
try {
|
|
95
|
+
actor = await Object$1.fromJsonLd(structuredClone(evidence.document), {
|
|
96
|
+
documentLoader: refuse,
|
|
97
|
+
contextLoader: options.contextLoader,
|
|
98
|
+
tracerProvider: options.tracerProvider
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (!(error instanceof TypeError)) throw error;
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
if (!isPortableActorDocument(actor) || getCanonicalPortableId(actor.id) !== evidence.ownerId || Temporal.Instant.compare(Temporal.Now.instant(), evidence.expires) >= 0) return null;
|
|
105
|
+
return actor;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { promoteKeyOwnerEvidence as a, parseKeyOwnerEvidence as i, attachKeyOwnerEvidence as n, getVerifiedKeyOwnerEvidence as r, MAX_PORTABLE_KEY_TTL as t };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { Temporal } from "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { t as MAX_PORTABLE_KEY_TTL } from "./key-owner-evidence-APB6NpSk.mjs";
|
|
5
|
+
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
6
|
+
//#region src/federation/keycache.ts
|
|
7
|
+
const KEY_CACHE_GENERATION = "2";
|
|
8
|
+
const COMPATIBLE_KEY_SEGMENT = "__compatible";
|
|
9
|
+
const MAX_OWNER_BYTES = 32 * 1024;
|
|
10
|
+
function isCompatibleKeyEntry(value) {
|
|
11
|
+
return value != null && typeof value === "object" && "key" in value && "expires" in value && typeof value.expires === "number";
|
|
12
|
+
}
|
|
13
|
+
function isJsonObject(value) {
|
|
14
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
var KvKeyCache = class {
|
|
17
|
+
kv;
|
|
18
|
+
prefix;
|
|
19
|
+
options;
|
|
20
|
+
unavailableKeyTtl;
|
|
21
|
+
keyTtl;
|
|
22
|
+
nullKeys;
|
|
23
|
+
constructor(kv, prefix, options = {}) {
|
|
24
|
+
this.kv = kv;
|
|
25
|
+
this.prefix = prefix;
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal.Duration.from({ minutes: 10 });
|
|
28
|
+
this.keyTtl = options.keyTtl ?? Temporal.Duration.from({ days: 30 });
|
|
29
|
+
this.nullKeys = /* @__PURE__ */ new Map();
|
|
30
|
+
}
|
|
31
|
+
#getFetchErrorKey(keyId) {
|
|
32
|
+
return [
|
|
33
|
+
...this.prefix,
|
|
34
|
+
"__fetchError",
|
|
35
|
+
keyId.href
|
|
36
|
+
];
|
|
37
|
+
}
|
|
38
|
+
#entryKey(keyId) {
|
|
39
|
+
return [
|
|
40
|
+
...this.prefix,
|
|
41
|
+
KEY_CACHE_GENERATION,
|
|
42
|
+
keyId.href
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
async get(keyId) {
|
|
46
|
+
const negativeExpiration = this.nullKeys.get(keyId.href);
|
|
47
|
+
if (negativeExpiration != null) {
|
|
48
|
+
if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
|
|
49
|
+
this.nullKeys.delete(keyId.href);
|
|
50
|
+
}
|
|
51
|
+
const serialized = await this.kv.get(this.#entryKey(keyId));
|
|
52
|
+
if (serialized === void 0) return void 0;
|
|
53
|
+
if (serialized === null) {
|
|
54
|
+
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return await CryptographicKey.fromJsonLd(serialized, this.options);
|
|
59
|
+
} catch {
|
|
60
|
+
try {
|
|
61
|
+
return await Multikey.fromJsonLd(serialized, this.options);
|
|
62
|
+
} catch {
|
|
63
|
+
await this.kv.delete(this.#entryKey(keyId));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async set(keyId, key) {
|
|
69
|
+
if (key == null) {
|
|
70
|
+
this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
|
|
71
|
+
await this.kv.set(this.#entryKey(keyId), null, { ttl: this.unavailableKeyTtl });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.nullKeys.delete(keyId.href);
|
|
75
|
+
const serialized = await key.toJsonLd(this.options);
|
|
76
|
+
await this.kv.set(this.#entryKey(keyId), serialized, { ttl: this.keyTtl });
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns the namespace of keys at FEP-ef61 compatible identifiers looked
|
|
80
|
+
* up for the given purpose. Its entries carry the time they expire, which
|
|
81
|
+
* is checked when they are read, so that an entry is never used after
|
|
82
|
+
* the proof that vouched for its key expired, however late the underlying
|
|
83
|
+
* store evicts it.
|
|
84
|
+
* @param scope The purpose of the lookups.
|
|
85
|
+
* @returns The namespace.
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
compatibleKeyScope(scope) {
|
|
89
|
+
const now = this.options.now ?? (() => Temporal.Now.instant());
|
|
90
|
+
const entryKey = (keyId) => [
|
|
91
|
+
...this.prefix,
|
|
92
|
+
COMPATIBLE_KEY_SEGMENT,
|
|
93
|
+
scope,
|
|
94
|
+
keyId.href
|
|
95
|
+
];
|
|
96
|
+
const isExpired = (entry) => entry.expires <= now().epochMilliseconds;
|
|
97
|
+
const keyTtl = Math.min(this.keyTtl.total("millisecond"), MAX_PORTABLE_KEY_TTL.total("millisecond"));
|
|
98
|
+
const unavailableKeyTtl = this.unavailableKeyTtl.total("millisecond");
|
|
99
|
+
const getEntry = async (keyId) => {
|
|
100
|
+
const entry = await this.kv.get(entryKey(keyId));
|
|
101
|
+
if (entry === void 0) return void 0;
|
|
102
|
+
if (!isCompatibleKeyEntry(entry) || isExpired(entry)) {
|
|
103
|
+
await this.kv.delete(entryKey(keyId));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (entry.key === null) return null;
|
|
107
|
+
let expires;
|
|
108
|
+
try {
|
|
109
|
+
expires = Temporal.Instant.fromEpochMilliseconds(entry.expires);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (!(error instanceof RangeError)) throw error;
|
|
112
|
+
await this.kv.delete(entryKey(keyId));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
let key;
|
|
116
|
+
try {
|
|
117
|
+
key = await CryptographicKey.fromJsonLd(entry.key, this.options);
|
|
118
|
+
} catch {
|
|
119
|
+
try {
|
|
120
|
+
key = await Multikey.fromJsonLd(entry.key, this.options);
|
|
121
|
+
} catch {
|
|
122
|
+
await this.kv.delete(entryKey(keyId));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (isExpired(entry)) return void 0;
|
|
127
|
+
return {
|
|
128
|
+
key,
|
|
129
|
+
...isJsonObject(entry.owner) ? { owner: entry.owner } : {},
|
|
130
|
+
expires
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
get: async (keyId) => {
|
|
135
|
+
const entry = await getEntry(keyId);
|
|
136
|
+
return entry == null ? entry : entry.key;
|
|
137
|
+
},
|
|
138
|
+
getEntry,
|
|
139
|
+
set: async (keyId, key, options) => {
|
|
140
|
+
const serialized = key == null ? null : await key.toJsonLd(this.options);
|
|
141
|
+
const current = now();
|
|
142
|
+
let expires = current.epochMilliseconds + (key == null ? unavailableKeyTtl : keyTtl);
|
|
143
|
+
if (options?.expires != null) expires = Math.min(expires, options.expires.epochMilliseconds);
|
|
144
|
+
const ttl = expires - current.epochMilliseconds;
|
|
145
|
+
if (ttl <= 0) {
|
|
146
|
+
await this.kv.delete(entryKey(keyId));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const owner = key == null || options?.owner == null || new TextEncoder().encode(JSON.stringify(options.owner)).length > MAX_OWNER_BYTES ? void 0 : options.owner;
|
|
150
|
+
await this.kv.set(entryKey(keyId), {
|
|
151
|
+
key: serialized,
|
|
152
|
+
expires,
|
|
153
|
+
...owner == null ? {} : { owner }
|
|
154
|
+
}, { ttl: Temporal.Duration.from({ milliseconds: ttl }) });
|
|
155
|
+
return key == null ? void 0 : Temporal.Instant.fromEpochMilliseconds(expires);
|
|
156
|
+
},
|
|
157
|
+
delete: async (keyId) => {
|
|
158
|
+
await this.kv.delete(entryKey(keyId));
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async getFetchError(keyId) {
|
|
163
|
+
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
|
|
164
|
+
if (cached == null || typeof cached !== "object") return void 0;
|
|
165
|
+
if ("status" in cached && typeof cached.status === "number" && "statusText" in cached && typeof cached.statusText === "string" && "headers" in cached && Array.isArray(cached.headers) && "body" in cached && typeof cached.body === "string") return {
|
|
166
|
+
status: cached.status,
|
|
167
|
+
response: new Response(cached.body, {
|
|
168
|
+
status: cached.status,
|
|
169
|
+
statusText: cached.statusText,
|
|
170
|
+
headers: cached.headers
|
|
171
|
+
})
|
|
172
|
+
};
|
|
173
|
+
else if ("errorName" in cached && typeof cached.errorName === "string" && "errorMessage" in cached && typeof cached.errorMessage === "string") {
|
|
174
|
+
const error = new Error(cached.errorMessage);
|
|
175
|
+
error.name = cached.errorName;
|
|
176
|
+
return { error };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async setFetchError(keyId, error) {
|
|
180
|
+
if (error == null) {
|
|
181
|
+
await this.kv.delete(this.#getFetchErrorKey(keyId));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if ("status" in error) {
|
|
185
|
+
await this.kv.set(this.#getFetchErrorKey(keyId), {
|
|
186
|
+
status: error.status,
|
|
187
|
+
statusText: error.response.statusText,
|
|
188
|
+
headers: Array.from(error.response.headers.entries()),
|
|
189
|
+
body: await error.response.clone().text()
|
|
190
|
+
}, { ttl: this.unavailableKeyTtl });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
await this.kv.set(this.#getFetchErrorKey(keyId), {
|
|
194
|
+
errorName: error.error.name,
|
|
195
|
+
errorMessage: error.error.message
|
|
196
|
+
}, { ttl: this.unavailableKeyTtl });
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
//#endregion
|
|
200
|
+
export { KvKeyCache as t };
|
|
@@ -41,14 +41,15 @@ interface KvStore {
|
|
|
41
41
|
/**
|
|
42
42
|
* Gets the value for the given key.
|
|
43
43
|
* @param key The key to get the value for.
|
|
44
|
-
* @returns The value for the key,
|
|
44
|
+
* @returns The value for the key, including `null` if stored, or `undefined`
|
|
45
|
+
* if the key does not exist.
|
|
45
46
|
* @template T The type of the value to get.
|
|
46
47
|
*/
|
|
47
48
|
get<T = unknown>(key: KvKey): Promise<T | undefined>;
|
|
48
49
|
/**
|
|
49
50
|
* Sets the value for the given key.
|
|
50
51
|
* @param key The key to set the value for.
|
|
51
|
-
* @param value The value to set.
|
|
52
|
+
* @param value The value to set. `null` is a storable value.
|
|
52
53
|
* @param options Additional options for setting the value.
|
|
53
54
|
*/
|
|
54
55
|
set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
|
|
@@ -41,14 +41,15 @@ interface KvStore {
|
|
|
41
41
|
/**
|
|
42
42
|
* Gets the value for the given key.
|
|
43
43
|
* @param key The key to get the value for.
|
|
44
|
-
* @returns The value for the key,
|
|
44
|
+
* @returns The value for the key, including `null` if stored, or `undefined`
|
|
45
|
+
* if the key does not exist.
|
|
45
46
|
* @template T The type of the value to get.
|
|
46
47
|
*/
|
|
47
48
|
get<T = unknown>(key: KvKey): Promise<T | undefined>;
|
|
48
49
|
/**
|
|
49
50
|
* Sets the value for the given key.
|
|
50
51
|
* @param key The key to set the value for.
|
|
51
|
-
* @param value The value to set.
|
|
52
|
+
* @param value The value to set. `null` is a storable value.
|
|
52
53
|
* @param options Additional options for setting the value.
|
|
53
54
|
*/
|
|
54
55
|
set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Temporal } from "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { t as MemoryKvStore } from "./kv-DCHkaJ9-.mjs";
|
|
5
|
+
//#region src/testing/kv.ts
|
|
6
|
+
/**
|
|
7
|
+
* A {@link KvStore} decorator whose entry expiry is driven by a virtual clock
|
|
8
|
+
* instead of wall-clock time.
|
|
9
|
+
*
|
|
10
|
+
* {@link MemoryKvStore} decides expiry by comparing timestamps rather than by
|
|
11
|
+
* scheduling timers, so a test that wants to observe a TTL elapsing would
|
|
12
|
+
* otherwise have to sleep for longer than the TTL and hope the runner keeps
|
|
13
|
+
* up. That makes such tests both slow and load-sensitive.
|
|
14
|
+
*
|
|
15
|
+
* This decorator keeps the TTLs itself and never passes them to the store it
|
|
16
|
+
* wraps, so nothing expires until {@link advance} moves the clock. Cache
|
|
17
|
+
* hits, expiry, and repopulation therefore all become deterministic, without
|
|
18
|
+
* patching any global and without a test-only entry point in the library
|
|
19
|
+
* itself. The clock starts at the Unix epoch and only ever moves forward.
|
|
20
|
+
*/
|
|
21
|
+
var ManualClockKvStore = class {
|
|
22
|
+
#inner;
|
|
23
|
+
#expirations = /* @__PURE__ */ new Map();
|
|
24
|
+
#now = Temporal.Instant.fromEpochMilliseconds(0);
|
|
25
|
+
/**
|
|
26
|
+
* Present only when the wrapped store has its own `cas()`. Callers such as
|
|
27
|
+
* the task deduplication planner test `kv.cas != null` to decide whether a
|
|
28
|
+
* conditional write is available, so the decorator must not advertise a
|
|
29
|
+
* capability the wrapped store lacks.
|
|
30
|
+
*/
|
|
31
|
+
cas;
|
|
32
|
+
constructor(inner = new MemoryKvStore()) {
|
|
33
|
+
this.#inner = inner;
|
|
34
|
+
const innerCas = inner.cas;
|
|
35
|
+
if (innerCas != null) this.cas = async (key, expectedValue, newValue, options) => {
|
|
36
|
+
await this.#evictIfExpired(key);
|
|
37
|
+
const swapped = await innerCas.call(inner, key, expectedValue, newValue);
|
|
38
|
+
if (swapped) this.#recordTtl(key, options);
|
|
39
|
+
return swapped;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The current reading of the virtual clock.
|
|
44
|
+
*/
|
|
45
|
+
get now() {
|
|
46
|
+
return this.#now;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Moves the virtual clock forward. Any entry whose TTL has elapsed by the
|
|
50
|
+
* new time reads as missing from this point on, exactly as it would once a
|
|
51
|
+
* real `KvStore` had let it expire.
|
|
52
|
+
*/
|
|
53
|
+
advance(duration) {
|
|
54
|
+
this.#now = this.#now.add(Temporal.Duration.from(duration).round({ largestUnit: "hour" }));
|
|
55
|
+
}
|
|
56
|
+
#encodeKey(key) {
|
|
57
|
+
return JSON.stringify(key);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Drops the entry if its TTL has elapsed. Mirrors {@link MemoryKvStore},
|
|
61
|
+
* which treats an entry as live up to and including its expiration instant.
|
|
62
|
+
*/
|
|
63
|
+
async #evictIfExpired(key) {
|
|
64
|
+
const encodedKey = this.#encodeKey(key);
|
|
65
|
+
const expiration = this.#expirations.get(encodedKey);
|
|
66
|
+
if (expiration == null) return;
|
|
67
|
+
if (this.#now.until(expiration).sign >= 0) return;
|
|
68
|
+
this.#expirations.delete(encodedKey);
|
|
69
|
+
await this.#inner.delete(key);
|
|
70
|
+
}
|
|
71
|
+
async #evictAllExpired() {
|
|
72
|
+
for (const [encodedKey, expiration] of [...this.#expirations]) {
|
|
73
|
+
if (this.#now.until(expiration).sign >= 0) continue;
|
|
74
|
+
this.#expirations.delete(encodedKey);
|
|
75
|
+
await this.#inner.delete(JSON.parse(encodedKey));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
#recordTtl(key, options) {
|
|
79
|
+
const encodedKey = this.#encodeKey(key);
|
|
80
|
+
if (options?.ttl == null) {
|
|
81
|
+
this.#expirations.delete(encodedKey);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.#expirations.set(encodedKey, this.#now.add(options.ttl.round({ largestUnit: "hour" })));
|
|
85
|
+
}
|
|
86
|
+
async get(key) {
|
|
87
|
+
await this.#evictIfExpired(key);
|
|
88
|
+
return await this.#inner.get(key);
|
|
89
|
+
}
|
|
90
|
+
async set(key, value, options) {
|
|
91
|
+
this.#recordTtl(key, options);
|
|
92
|
+
await this.#inner.set(key, value);
|
|
93
|
+
}
|
|
94
|
+
async delete(key) {
|
|
95
|
+
this.#expirations.delete(this.#encodeKey(key));
|
|
96
|
+
await this.#inner.delete(key);
|
|
97
|
+
}
|
|
98
|
+
async *list(prefix) {
|
|
99
|
+
await this.#evictAllExpired();
|
|
100
|
+
yield* this.#inner.list(prefix);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
export { ManualClockKvStore as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { p as recordDocumentCache } from "./metrics-
|
|
4
|
+
import { p as recordDocumentCache } from "./metrics-CYg9yxBL.mjs";
|
|
5
5
|
import { getLogger } from "@logtape/logtape";
|
|
6
6
|
import { preloadedContexts } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/utils/kv-cache.ts
|
|
@@ -1,47 +1,85 @@
|
|
|
1
1
|
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
3
|
+
import { G as validateCryptoKey, ot as recordDocumentCache, t as doubleKnock } from "./http-DydWCB7R.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
|
+
import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, resolveDocumentLoaderTimeout, validatePublicUrl, withDocumentLoaderTimeout } from "@fedify/vocab-runtime";
|
|
5
6
|
import { curry } from "es-toolkit";
|
|
6
|
-
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/utils/docloader.ts
|
|
8
8
|
const logger$1 = getLogger([
|
|
9
9
|
"fedify",
|
|
10
10
|
"utils",
|
|
11
11
|
"docloader"
|
|
12
12
|
]);
|
|
13
|
+
const DEFAULT_MAX_REDIRECTION = 20;
|
|
13
14
|
/**
|
|
14
15
|
* Gets an authenticated {@link DocumentLoader} for the given identity.
|
|
15
16
|
* Note that an authenticated document loader intentionally does not cache
|
|
16
17
|
* the fetched documents.
|
|
18
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
19
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
20
|
+
* Each call, including its double-knocking retries, times out after
|
|
21
|
+
* 10 seconds by default; see {@link DocumentLoaderFactoryOptions.timeout}.
|
|
17
22
|
* @param identity The identity to get the document loader for.
|
|
18
23
|
* The actor's key pair.
|
|
19
24
|
* @param options The options for the document loader.
|
|
20
25
|
* @returns The authenticated document loader.
|
|
21
26
|
* @throws {TypeError} If the key is invalid or unsupported.
|
|
27
|
+
* @throws {RangeError} If the `timeout` option is invalid.
|
|
22
28
|
* @since 0.4.0
|
|
23
29
|
*/
|
|
24
|
-
function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
|
|
30
|
+
function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, timeout, tracerProvider } = {}) {
|
|
25
31
|
validateCryptoKey(identity.privateKey);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const resolvedTimeout = resolveDocumentLoaderTimeout(timeout);
|
|
33
|
+
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
34
|
+
options?.signal?.throwIfAborted();
|
|
35
|
+
let currentUrl = new URL(url).href;
|
|
36
|
+
await validateUrl(currentUrl, options);
|
|
37
|
+
visited.add(currentUrl);
|
|
38
|
+
const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
|
|
39
|
+
function follow(nextUrl) {
|
|
40
|
+
options?.signal?.throwIfAborted();
|
|
41
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
42
|
+
if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
|
|
43
|
+
redirected++;
|
|
35
44
|
}
|
|
36
|
-
|
|
45
|
+
const response = await doubleKnock(originalRequest, identity, {
|
|
37
46
|
maxRedirection,
|
|
38
47
|
specDeterminer,
|
|
39
48
|
log: curry(logRequest)(logger$1),
|
|
40
49
|
tracerProvider,
|
|
41
|
-
signal: options?.signal
|
|
42
|
-
|
|
50
|
+
signal: options?.signal,
|
|
51
|
+
validateRedirect: async (nextUrl) => {
|
|
52
|
+
follow(nextUrl);
|
|
53
|
+
await validateUrl(nextUrl, options);
|
|
54
|
+
visited.add(nextUrl);
|
|
55
|
+
currentUrl = nextUrl;
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
options?.signal?.throwIfAborted();
|
|
59
|
+
return getRemoteDocument(currentUrl, response, (alternateUrl) => {
|
|
60
|
+
follow(alternateUrl);
|
|
61
|
+
return load(alternateUrl, options, redirected, visited);
|
|
62
|
+
}, options);
|
|
63
|
+
}
|
|
64
|
+
async function validateUrl(url, options) {
|
|
65
|
+
if (!allowPrivateAddress) {
|
|
66
|
+
try {
|
|
67
|
+
await validatePublicUrl(url);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
|
|
70
|
+
url,
|
|
71
|
+
error
|
|
72
|
+
});
|
|
73
|
+
else logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
|
|
74
|
+
url,
|
|
75
|
+
error
|
|
76
|
+
});
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
options?.signal?.throwIfAborted();
|
|
80
|
+
}
|
|
43
81
|
}
|
|
44
|
-
return load;
|
|
82
|
+
return withDocumentLoaderTimeout((url, options) => load(url, options), resolvedTimeout);
|
|
45
83
|
}
|
|
46
84
|
//#endregion
|
|
47
85
|
//#region src/utils/kv-cache.ts
|
|
@@ -1,52 +1,88 @@
|
|
|
1
1
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
2
|
-
require("./
|
|
3
|
-
const require_http = require("./http-hlIjmewS.cjs");
|
|
4
|
-
const require_temporal_polyfill = require("./temporal-polyfill-BCdTHexd.cjs");
|
|
2
|
+
const require_http = require("./http-B5JT115n.cjs");
|
|
5
3
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
|
-
let es_toolkit = require("es-toolkit");
|
|
7
4
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
5
|
+
let es_toolkit = require("es-toolkit");
|
|
8
6
|
//#region src/utils/docloader.ts
|
|
9
7
|
const logger$1 = (0, _logtape_logtape.getLogger)([
|
|
10
8
|
"fedify",
|
|
11
9
|
"utils",
|
|
12
10
|
"docloader"
|
|
13
11
|
]);
|
|
12
|
+
const DEFAULT_MAX_REDIRECTION = 20;
|
|
14
13
|
/**
|
|
15
14
|
* Gets an authenticated {@link DocumentLoader} for the given identity.
|
|
16
15
|
* Note that an authenticated document loader intentionally does not cache
|
|
17
16
|
* the fetched documents.
|
|
17
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
18
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
19
|
+
* Each call, including its double-knocking retries, times out after
|
|
20
|
+
* 10 seconds by default; see {@link DocumentLoaderFactoryOptions.timeout}.
|
|
18
21
|
* @param identity The identity to get the document loader for.
|
|
19
22
|
* The actor's key pair.
|
|
20
23
|
* @param options The options for the document loader.
|
|
21
24
|
* @returns The authenticated document loader.
|
|
22
25
|
* @throws {TypeError} If the key is invalid or unsupported.
|
|
26
|
+
* @throws {RangeError} If the `timeout` option is invalid.
|
|
23
27
|
* @since 0.4.0
|
|
24
28
|
*/
|
|
25
|
-
function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
|
|
29
|
+
function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, timeout, tracerProvider } = {}) {
|
|
26
30
|
require_http.validateCryptoKey(identity.privateKey);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
const resolvedTimeout = (0, _fedify_vocab_runtime.resolveDocumentLoaderTimeout)(timeout);
|
|
32
|
+
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
33
|
+
options?.signal?.throwIfAborted();
|
|
34
|
+
let currentUrl = new URL(url).href;
|
|
35
|
+
await validateUrl(currentUrl, options);
|
|
36
|
+
visited.add(currentUrl);
|
|
37
|
+
const originalRequest = (0, _fedify_vocab_runtime.createActivityPubRequest)(currentUrl, { userAgent });
|
|
38
|
+
function follow(nextUrl) {
|
|
39
|
+
options?.signal?.throwIfAborted();
|
|
40
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
41
|
+
if (visited.has(nextUrl)) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
|
|
42
|
+
redirected++;
|
|
36
43
|
}
|
|
37
|
-
|
|
44
|
+
const response = await require_http.doubleKnock(originalRequest, identity, {
|
|
38
45
|
maxRedirection,
|
|
39
46
|
specDeterminer,
|
|
40
47
|
log: (0, es_toolkit.curry)(_fedify_vocab_runtime.logRequest)(logger$1),
|
|
41
48
|
tracerProvider,
|
|
42
|
-
signal: options?.signal
|
|
43
|
-
|
|
49
|
+
signal: options?.signal,
|
|
50
|
+
validateRedirect: async (nextUrl) => {
|
|
51
|
+
follow(nextUrl);
|
|
52
|
+
await validateUrl(nextUrl, options);
|
|
53
|
+
visited.add(nextUrl);
|
|
54
|
+
currentUrl = nextUrl;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
options?.signal?.throwIfAborted();
|
|
58
|
+
return (0, _fedify_vocab_runtime.getRemoteDocument)(currentUrl, response, (alternateUrl) => {
|
|
59
|
+
follow(alternateUrl);
|
|
60
|
+
return load(alternateUrl, options, redirected, visited);
|
|
61
|
+
}, options);
|
|
62
|
+
}
|
|
63
|
+
async function validateUrl(url, options) {
|
|
64
|
+
if (!allowPrivateAddress) {
|
|
65
|
+
try {
|
|
66
|
+
await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof _fedify_vocab_runtime.UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
|
|
69
|
+
url,
|
|
70
|
+
error
|
|
71
|
+
});
|
|
72
|
+
else logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
|
|
73
|
+
url,
|
|
74
|
+
error
|
|
75
|
+
});
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
options?.signal?.throwIfAborted();
|
|
79
|
+
}
|
|
44
80
|
}
|
|
45
|
-
return load;
|
|
81
|
+
return (0, _fedify_vocab_runtime.withDocumentLoaderTimeout)((url, options) => load(url, options), resolvedTimeout);
|
|
46
82
|
}
|
|
47
83
|
//#endregion
|
|
48
84
|
//#region src/utils/kv-cache.ts
|
|
49
|
-
const Temporal =
|
|
85
|
+
const Temporal = require_http.Temporal;
|
|
50
86
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
51
87
|
"fedify",
|
|
52
88
|
"utils",
|