@fedify/fedify 1.9.6 → 1.9.8
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/{actor-CEGEmRll.js → actor-BTA45fXF.js} +1205 -2768
- package/dist/{actor-Ydzhc8dj.d.cts → actor-Be-68iJP.d.cts} +3 -3
- package/dist/{actor-C1Euqngb.d.ts → actor-C5AY0Tno.d.ts} +3 -3
- package/dist/{actor-DbpZ6pzg.js → actor-DGa1EWaV.mjs} +8 -15
- package/dist/{actor-DlS-Q8hE.cjs → actor-DxfJk4lY.cjs} +3112 -4674
- package/dist/{assert-MZs1qjMx.js → assert-DikXweDx.mjs} +5 -9
- package/dist/{assert_equals-DSbWqCm3.js → assert_equals-Ew3jOFa3.mjs} +55 -69
- package/dist/{assert_instance_of-DHz7EHNU.js → assert_instance_of-C4Ri6VuN.mjs} +5 -9
- package/dist/{assert_is_error-BPGph1Jx.js → assert_is_error-C50x8tnw.mjs} +5 -9
- package/dist/{assert_not_equals-f3m3epl3.js → assert_not_equals--wG9hV7u.mjs} +6 -13
- package/dist/{assert_rejects-DiIiJbZn.js → assert_rejects-CJC9ThS-.mjs} +6 -11
- package/dist/{assert_throws-BOO88avQ.js → assert_throws-BIL7gChy.mjs} +6 -10
- package/dist/{authdocloader-DUQcOTRS.js → authdocloader-BC2rYCy1.mjs} +9 -14
- package/dist/{authdocloader-CT_V4Z7G.cjs → authdocloader-BrhFB421.cjs} +14 -22
- package/dist/{authdocloader-BLqMyboS.js → authdocloader-CqtNsX_N.js} +8 -15
- package/dist/{builder-BO61xeXE.js → builder-CIkAhIGC.mjs} +31 -40
- package/dist/{client-DF8anIB5.d.ts → client-D8OSiPBT.d.ts} +2 -2
- package/dist/{client-UG5wpNhG.js → client-MXqit6c-.mjs} +11 -15
- package/dist/{client-DjT_tegg.d.cts → client-T0VFOdMw.d.cts} +2 -2
- package/dist/{collection-CSzG2j1P.js → collection-BD6-SZ6O.mjs} +7 -12
- package/dist/compat/mod.cjs +5 -8
- package/dist/compat/mod.d.cts +78 -12
- package/dist/compat/mod.d.ts +78 -12
- package/dist/compat/mod.js +4 -8
- package/dist/compat/transformers.test.mjs +62 -0
- package/dist/{context-CwUAkopp.d.cts → context-CACMqDzl.d.cts} +33 -26
- package/dist/{context-CXUibY4L.d.ts → context-K4cCphQj.d.ts} +33 -26
- package/dist/{denokv-Bv33Xxea.js → denokv-CoSB_Eps.mjs} +22 -11
- package/dist/{docloader-BIFI3OS7.cjs → docloader-BVuUhBLI.cjs} +112 -212
- package/dist/{docloader-fJgJeqiX.js → docloader-BoXhusJ1.js} +17 -151
- package/dist/{docloader-CxWcuWqQ.d.ts → docloader-DSaLRXEA.d.ts} +2 -7
- package/dist/{docloader-D-MrRyHl.d.cts → docloader-DpGRDZrn.d.cts} +2 -7
- package/dist/{esm-C1EfGjSS.js → esm-BHJ7sdNg.mjs} +49 -85
- package/dist/federation/{builder.test.js → builder.test.mjs} +22 -41
- package/dist/federation/collection.test.d.mts +2 -0
- package/dist/federation/collection.test.mjs +21 -0
- package/dist/federation/handler.test.d.mts +2 -0
- package/dist/federation/{handler.test.js → handler.test.mjs} +22 -49
- package/dist/federation/idempotency.test.d.mts +2 -0
- package/dist/federation/{idempotency.test.js → idempotency.test.mjs} +29 -59
- package/dist/federation/inbox.test.d.mts +2 -0
- package/dist/federation/{inbox.test.js → inbox.test.mjs} +10 -15
- package/dist/federation/keycache.test.d.mts +2 -0
- package/dist/federation/{keycache.test.js → keycache.test.mjs} +13 -18
- package/dist/federation/kv.test.d.mts +2 -0
- package/dist/federation/{kv.test.js → kv.test.mjs} +9 -20
- package/dist/federation/middleware.test.d.mts +2 -0
- package/dist/federation/{middleware.test.js → middleware.test.mjs} +182 -197
- package/dist/federation/mod.cjs +223 -21
- package/dist/federation/mod.d.cts +4 -13
- package/dist/federation/mod.d.ts +4 -13
- package/dist/federation/mod.js +218 -18
- package/dist/federation/mq.test.d.mts +2 -0
- package/dist/federation/{mq.test.js → mq.test.mjs} +23 -38
- package/dist/federation/retry.test.d.mts +2 -0
- package/dist/federation/{retry.test.js → retry.test.mjs} +9 -14
- package/dist/federation/router.test.d.mts +2 -0
- package/dist/federation/{router.test.js → router.test.mjs} +12 -19
- package/dist/federation/send.test.d.mts +2 -0
- package/dist/federation/{send.test.js → send.test.mjs} +17 -26
- package/dist/{http-wsGR6KkT.d.ts → http-BZpls--H.d.ts} +5 -9
- package/dist/{http-CR-Eg1Uq.js → http-Bu5ZNlhZ.mjs} +16 -32
- package/dist/{http-B1_DzfAU.d.cts → http-C7WoprmE.d.cts} +5 -9
- package/dist/{http-BgopPF-8.cjs → http-DKw-O_VY.cjs} +51 -68
- package/dist/{http-05HxN-lp.js → http-VJbz6sKD.js} +17 -33
- package/dist/{inbox-DcJN1cxM.js → inbox-DkbTULXE.mjs} +17 -25
- package/dist/key-4fu6v0Jf.mjs +5 -0
- package/dist/{key-DjS1X9TG.cjs → key-B-wFdaPB.cjs} +42 -50
- package/dist/{key-ibMO03_0.js → key-BNMK_IVr.mjs} +12 -18
- package/dist/key-CancShOo.cjs +4 -0
- package/dist/{key-CPJcJjp-.js → key-DK3o0FEH.js} +19 -19
- package/dist/{keycache-CMUfqYqr.js → keycache-D-Vj8z88.mjs} +6 -10
- package/dist/{keys-IZ5050fT.js → keys-B27nVeIs.mjs} +6 -10
- package/dist/{kv-C7sopW2E.d.ts → kv-Bq9QLKm5.d.ts} +1 -1
- package/dist/{kv-CRZrzyXm.js → kv-DM1zFCtL.mjs} +6 -10
- package/dist/{kv-63Cil1MD.d.cts → kv-GIrOktyG.d.cts} +1 -1
- package/dist/{ld-DHNA2RSQ.js → ld-6jAVu3jV.mjs} +17 -31
- package/dist/{lookup-CKZfuyxA.js → lookup-BaU75j-d.js} +5 -11
- package/dist/{lookup-C4_dVYz7.cjs → lookup-BiIPmTwB.cjs} +16 -23
- package/dist/{lookup-BMAWLsP2.js → lookup-DmeJ8WUw.mjs} +8 -17
- package/dist/middleware-9ByEpBvV.cjs +4 -0
- package/dist/{middleware-CGbvIGvy.cjs → middleware-C188G4Go.cjs} +494 -547
- package/dist/{middleware-DrhEvfTo.js → middleware-Db1yZQaT.mjs} +276 -321
- package/dist/{middleware-ODfDRN3q.js → middleware-Do06X21v.js} +349 -393
- package/dist/middleware-DrfZEjyc.mjs +5 -0
- package/dist/{mod-CDObsV1d.d.ts → mod-BSwc3_rD.d.ts} +3 -3
- package/dist/{mod-DBzN0aCM.d.ts → mod-BTNpXcPj.d.ts} +2 -2
- package/dist/{mod-fjqfsrty.d.cts → mod-Chb_NKPp.d.cts} +4 -4
- package/dist/{mod-DgcYoyZK.d.ts → mod-DHoc3toL.d.ts} +4 -4
- package/dist/{mod-jQ4OODsl.d.cts → mod-DIMx6YjJ.d.cts} +2 -2
- package/dist/{mod-BUbqxBev.d.cts → mod-HElaq2UB.d.cts} +3 -3
- package/dist/mod.cjs +31 -33
- package/dist/mod.d.cts +15 -17
- package/dist/mod.d.ts +15 -17
- package/dist/mod.js +20 -23
- package/dist/{mq-B7R1Q-M5.d.cts → mq-CrItclRD.d.cts} +1 -1
- package/dist/{mq-CRGm1e_F.d.ts → mq-D_ZZRdby.d.ts} +1 -1
- package/dist/nodeinfo/client.test.d.mts +2 -0
- package/dist/nodeinfo/{client.test.js → client.test.mjs} +23 -45
- package/dist/nodeinfo/handler.test.d.mts +2 -0
- package/dist/nodeinfo/{handler.test.js → handler.test.mjs} +14 -43
- package/dist/nodeinfo/mod.cjs +5 -9
- package/dist/nodeinfo/mod.d.cts +2 -5
- package/dist/nodeinfo/mod.d.ts +2 -5
- package/dist/nodeinfo/mod.js +4 -9
- package/dist/nodeinfo/semver.test.d.mts +2 -0
- package/dist/nodeinfo/{semver.test.js → semver.test.mjs} +28 -51
- package/dist/nodeinfo/types.test.d.mts +2 -0
- package/dist/nodeinfo/{types.test.js → types.test.mjs} +10 -21
- package/dist/{owner-DDHsHYQO.js → owner-CKuGt_T9.mjs} +10 -13
- package/dist/{owner-BbeUDvOu.d.ts → owner-DPAPnB0R.d.ts} +4 -4
- package/dist/{owner-6KSEp9eV.d.cts → owner-_rFs0ik_.d.cts} +4 -4
- package/dist/{proof-V1uQaB2y.js → proof-CmTtG_t-.js} +33 -57
- package/dist/{proof-CX7ujFFX.cjs → proof-DLOy7HYU.cjs} +112 -135
- package/dist/{proof-exgGRW88.js → proof-mJLL2gSA.mjs} +20 -32
- package/dist/{retry-D4GJ670a.js → retry-Ddbq3AcK.mjs} +4 -7
- package/dist/rolldown-runtime-C7fyD9f2.js +15 -0
- package/dist/runtime/authdocloader.test.d.mts +2 -0
- package/dist/runtime/{authdocloader.test.js → authdocloader.test.mjs} +16 -28
- package/dist/runtime/docloader.test.d.mts +2 -0
- package/dist/runtime/{docloader.test.js → docloader.test.mjs} +52 -72
- package/dist/runtime/key.test.d.mts +2 -0
- package/dist/runtime/{key.test.js → key.test.mjs} +27 -57
- package/dist/runtime/langstr.test.d.mts +2 -0
- package/dist/runtime/{langstr.test.js → langstr.test.mjs} +9 -19
- package/dist/runtime/link.test.d.mts +2 -0
- package/dist/runtime/{link.test.js → link.test.mjs} +7 -11
- package/dist/runtime/mod.cjs +7 -13
- package/dist/runtime/mod.d.cts +103 -6
- package/dist/runtime/mod.d.ts +103 -6
- package/dist/runtime/mod.js +6 -13
- package/dist/runtime/multibase/multibase.test.d.mts +2 -0
- package/dist/runtime/multibase/{multibase.test.js → multibase.test.mjs} +16 -33
- package/dist/runtime/url.test.d.mts +2 -0
- package/dist/runtime/{url.test.js → url.test.mjs} +10 -15
- package/dist/{semver-dArNLkR9.js → semver-CgD82xxg.mjs} +13 -28
- package/dist/{send-BfMYakUE.js → send-BsQbGuw4.mjs} +7 -12
- package/dist/sig/http.test.d.mts +2 -0
- package/dist/sig/{http.test.js → http.test.mjs} +119 -203
- package/dist/sig/key.test.d.mts +2 -0
- package/dist/sig/{key.test.js → key.test.mjs} +13 -22
- package/dist/sig/ld.test.d.mts +2 -0
- package/dist/sig/{ld.test.js → ld.test.mjs} +24 -39
- package/dist/sig/mod.cjs +7 -13
- package/dist/sig/mod.d.cts +3 -7
- package/dist/sig/mod.d.ts +3 -7
- package/dist/sig/mod.js +6 -13
- package/dist/sig/owner.test.d.mts +2 -0
- package/dist/sig/owner.test.mjs +39 -0
- package/dist/sig/proof.test.d.mts +2 -0
- package/dist/sig/{proof.test.js → proof.test.mjs} +18 -31
- package/dist/{std__assert-X-_kMxKM.js → std__assert-2v7gYiZp.mjs} +13 -24
- package/dist/testing/docloader.test.d.mts +2 -0
- package/dist/testing/docloader.test.mjs +12 -0
- package/dist/testing/{mod.d.ts → mod.d.mts} +371 -541
- package/dist/testing/mod.mjs +5 -0
- package/dist/{testing-RPOc_gVG.js → testing-DS3gcq8V.mjs} +27 -35
- package/dist/{transformers-CoBS-oFG.cjs → transformers-BM0M8hnW.cjs} +20 -25
- package/dist/{transformers-BFT6d7J5.js → transformers-BV4OeK9o.js} +3 -7
- package/dist/{types-Cptev2nt.js → types-BXfL-dsX.js} +18 -36
- package/dist/{types-BIgY6c-l.js → types-CAnkAQGM.mjs} +5 -9
- package/dist/{types-CGnM1vft.cjs → types-DpM4FhjW.cjs} +45 -64
- package/dist/vocab/actor.test.d.mts +2 -0
- package/dist/vocab/{actor.test.js → actor.test.mjs} +308 -550
- package/dist/vocab/cjs.test.d.mts +2 -0
- package/dist/vocab/cjs.test.mjs +14 -0
- package/dist/vocab/lookup.test.d.mts +2 -0
- package/dist/vocab/{lookup.test.js → lookup.test.mjs} +29 -45
- package/dist/vocab/mod.cjs +251 -16
- package/dist/vocab/mod.d.cts +3 -5
- package/dist/vocab/mod.d.ts +3 -5
- package/dist/vocab/mod.js +244 -10
- package/dist/vocab/type.test.d.mts +2 -0
- package/dist/vocab/type.test.mjs +16 -0
- package/dist/vocab/vocab.test.d.mts +2 -0
- package/dist/vocab/{vocab.test.js → vocab.test.mjs} +178 -311
- package/dist/{vocab-CDHNj5zp.d.ts → vocab-BLvSEtuz.d.cts} +2 -4
- package/dist/{type-COPv6pMi.js → vocab-DuW9rL1h.mjs} +1177 -2871
- package/dist/{vocab-Cfs0937i.d.cts → vocab-lhCS9lzq.d.ts} +4 -2
- package/dist/webfinger/handler.test.d.mts +2 -0
- package/dist/webfinger/{handler.test.js → handler.test.mjs} +23 -56
- package/dist/webfinger/lookup.test.d.mts +2 -0
- package/dist/webfinger/{lookup.test.js → lookup.test.mjs} +13 -27
- package/dist/webfinger/mod.cjs +5 -9
- package/dist/webfinger/mod.d.cts +1 -3
- package/dist/webfinger/mod.d.ts +1 -3
- package/dist/webfinger/mod.js +4 -9
- package/dist/x/cfworkers.cjs +25 -14
- package/dist/x/cfworkers.d.cts +33 -6
- package/dist/x/cfworkers.d.ts +33 -6
- package/dist/x/cfworkers.js +22 -12
- package/dist/x/cfworkers.test.d.mts +2 -0
- package/dist/x/{cfworkers.test.js → cfworkers.test.mjs} +28 -26
- package/dist/x/hono.cjs +25 -14
- package/dist/x/hono.d.cts +1 -11
- package/dist/x/hono.d.ts +1 -11
- package/dist/x/hono.js +22 -12
- package/dist/x/sveltekit.cjs +23 -12
- package/dist/x/sveltekit.d.cts +1 -11
- package/dist/x/sveltekit.d.ts +1 -11
- package/dist/x/sveltekit.js +20 -10
- package/package.json +2 -2
- package/dist/chunk-DqRYRqnO.cjs +0 -34
- package/dist/compat/transformers.test.d.ts +0 -3
- package/dist/compat/transformers.test.js +0 -86
- package/dist/compat-DmDDELst.cjs +0 -4
- package/dist/compat-nxUqe4Z-.js +0 -4
- package/dist/federation/builder.test.d.ts +0 -3
- package/dist/federation/collection.test.d.ts +0 -3
- package/dist/federation/collection.test.js +0 -35
- package/dist/federation/handler.test.d.ts +0 -3
- package/dist/federation/idempotency.test.d.ts +0 -3
- package/dist/federation/inbox.test.d.ts +0 -3
- package/dist/federation/keycache.test.d.ts +0 -3
- package/dist/federation/kv.test.d.ts +0 -3
- package/dist/federation/middleware.test.d.ts +0 -3
- package/dist/federation/mq.test.d.ts +0 -3
- package/dist/federation/retry.test.d.ts +0 -3
- package/dist/federation/router.test.d.ts +0 -3
- package/dist/federation/send.test.d.ts +0 -3
- package/dist/federation-D1U8YY9t.js +0 -226
- package/dist/federation-H2_En3j5.cjs +0 -244
- package/dist/key-BCUd8FWp.js +0 -10
- package/dist/key-BUardnTH.cjs +0 -10
- package/dist/key-Dr6H_e3K.js +0 -10
- package/dist/middleware-BJ83veqi.js +0 -26
- package/dist/middleware-CJ4W2ir5.cjs +0 -17
- package/dist/middleware-Ve2mHJgo.js +0 -17
- package/dist/mod-BcObK1Lz.d.ts +0 -82
- package/dist/mod-C2tOeRkN.d.cts +0 -1
- package/dist/mod-CIbqfZW0.d.ts +0 -104
- package/dist/mod-Dt-G9ZOS.d.cts +0 -102
- package/dist/mod-FZd39qVq.d.cts +0 -1
- package/dist/mod-mXx9V0q5.d.cts +0 -80
- package/dist/nodeinfo/client.test.d.ts +0 -3
- package/dist/nodeinfo/handler.test.d.ts +0 -3
- package/dist/nodeinfo/semver.test.d.ts +0 -3
- package/dist/nodeinfo/types.test.d.ts +0 -3
- package/dist/nodeinfo-Co9lJrWl.cjs +0 -4
- package/dist/nodeinfo-DfycQ8Wf.js +0 -4
- package/dist/runtime/authdocloader.test.d.ts +0 -3
- package/dist/runtime/docloader.test.d.ts +0 -3
- package/dist/runtime/key.test.d.ts +0 -3
- package/dist/runtime/langstr.test.d.ts +0 -3
- package/dist/runtime/link.test.d.ts +0 -3
- package/dist/runtime/multibase/multibase.test.d.ts +0 -3
- package/dist/runtime/url.test.d.ts +0 -3
- package/dist/runtime-C58AJWSv.cjs +0 -4
- package/dist/runtime-DPYEDf-o.js +0 -4
- package/dist/sig/http.test.d.ts +0 -3
- package/dist/sig/key.test.d.ts +0 -3
- package/dist/sig/ld.test.d.ts +0 -3
- package/dist/sig/owner.test.d.ts +0 -3
- package/dist/sig/owner.test.js +0 -52
- package/dist/sig/proof.test.d.ts +0 -3
- package/dist/sig-ByHXzqUi.cjs +0 -4
- package/dist/sig-Cj3tk-ig.js +0 -4
- package/dist/testing/docloader.test.d.ts +0 -3
- package/dist/testing/docloader.test.js +0 -24
- package/dist/testing/mod.js +0 -10
- package/dist/vocab/actor.test.d.ts +0 -3
- package/dist/vocab/lookup.test.d.ts +0 -3
- package/dist/vocab/type.test.d.ts +0 -3
- package/dist/vocab/type.test.js +0 -25
- package/dist/vocab/vocab.test.d.ts +0 -3
- package/dist/vocab-BFy1CS5L.cjs +0 -289
- package/dist/vocab-BPFiQ650.js +0 -253
- package/dist/webfinger/handler.test.d.ts +0 -3
- package/dist/webfinger/lookup.test.d.ts +0 -3
- package/dist/webfinger-BjOEdFPs.cjs +0 -4
- package/dist/webfinger-De_bU0iE.js +0 -4
- package/dist/x/cfworkers.test.d.ts +0 -3
- /package/dist/{mod-1pDWKvUL.d.ts → compat/transformers.test.d.mts} +0 -0
- /package/dist/{mod-g0xFzAP9.d.ts → federation/builder.test.d.mts} +0 -0
|
@@ -1,38 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import "../
|
|
10
|
-
import "../
|
|
11
|
-
import "../
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { assertExists, assertStringIncludes } from "../std__assert-X-_kMxKM.js";
|
|
16
|
-
import { assertFalse, assertRejects } from "../assert_rejects-DiIiJbZn.js";
|
|
17
|
-
import "../assert_is_error-BPGph1Jx.js";
|
|
18
|
-
import "../assert_not_equals-f3m3epl3.js";
|
|
19
|
-
import { assertThrows } from "../assert_throws-BOO88avQ.js";
|
|
20
|
-
import { rsaPrivateKey2, rsaPublicKey1, rsaPublicKey2, rsaPublicKey5 } from "../keys-IZ5050fT.js";
|
|
21
|
-
import { esm_default } from "../esm-C1EfGjSS.js";
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { M as exportSpki } from "../vocab-DuW9rL1h.mjs";
|
|
5
|
+
import { a as mockDocumentLoader, t as test } from "../testing-DS3gcq8V.mjs";
|
|
6
|
+
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
7
|
+
import { a as assertExists, t as assertStringIncludes } from "../std__assert-2v7gYiZp.mjs";
|
|
8
|
+
import { n as assertFalse, t as assertRejects } from "../assert_rejects-CJC9ThS-.mjs";
|
|
9
|
+
import { t as assertThrows } from "../assert_throws-BIL7gChy.mjs";
|
|
10
|
+
import { t as assert } from "../assert-DikXweDx.mjs";
|
|
11
|
+
import { t as exportJwk } from "../key-BNMK_IVr.mjs";
|
|
12
|
+
import { a as parseRfc9421Signature, c as timingSafeEqual, i as formatRfc9421SignatureParameters, l as verifyRequest, n as doubleKnock, o as parseRfc9421SignatureInput, r as formatRfc9421Signature, s as signRequest, t as createRfc9421SignatureBase } from "../http-Bu5ZNlhZ.mjs";
|
|
13
|
+
import { t as esm_default } from "../esm-BHJ7sdNg.mjs";
|
|
14
|
+
import { i as rsaPrivateKey2, l as rsaPublicKey5, o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-B27nVeIs.mjs";
|
|
22
15
|
import { encodeBase64 } from "byte-encodings/base64";
|
|
23
|
-
|
|
24
16
|
//#region src/sig/http.test.ts
|
|
25
17
|
test("signRequest() [draft-cavage]", async () => {
|
|
26
|
-
|
|
18
|
+
assertEquals(await verifyRequest(await signRequest(new Request("https://example.com/", {
|
|
27
19
|
method: "POST",
|
|
28
20
|
body: "Hello, world!",
|
|
29
21
|
headers: {
|
|
30
22
|
"Content-Type": "text/plain; charset=utf-8",
|
|
31
23
|
Accept: "text/plain"
|
|
32
24
|
}
|
|
33
|
-
})
|
|
34
|
-
const signed = await signRequest(request, rsaPrivateKey2, new URL("https://example.com/key2"));
|
|
35
|
-
assertEquals(await verifyRequest(signed, {
|
|
25
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2")), {
|
|
36
26
|
contextLoader: mockDocumentLoader,
|
|
37
27
|
documentLoader: mockDocumentLoader
|
|
38
28
|
}), rsaPublicKey2);
|
|
@@ -58,8 +48,8 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
58
48
|
get(keyId) {
|
|
59
49
|
return Promise.resolve(cache[keyId.href]);
|
|
60
50
|
},
|
|
61
|
-
set(keyId, key
|
|
62
|
-
cache[keyId.href] = key
|
|
51
|
+
set(keyId, key) {
|
|
52
|
+
cache[keyId.href] = key;
|
|
63
53
|
return Promise.resolve();
|
|
64
54
|
}
|
|
65
55
|
}
|
|
@@ -147,7 +137,7 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
147
137
|
currentTime: Temporal.Instant.from("2025-01-01T00:00:00.0000Z"),
|
|
148
138
|
timeWindow: false
|
|
149
139
|
}), rsaPublicKey1);
|
|
150
|
-
|
|
140
|
+
assert(await verifyRequest(new Request("https://c27a97f98d5f.ngrok.app/i/inbox", {
|
|
151
141
|
method: "POST",
|
|
152
142
|
body: "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://w3id.org/security/v1\"],\"actor\":\"https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd\",\"object\":{\"actor\":\"https://c27a97f98d5f.ngrok.app/i\",\"object\":\"https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd\",\"type\":\"Follow\",\"id\":\"https://c27a97f98d5f.ngrok.app/i#follows/https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd\"},\"type\":\"Accept\",\"id\":\"https://oeee.cafe/objects/0fc2608f-5660-4b91-b8c7-63c0c2ac2e20\"}",
|
|
153
143
|
headers: {
|
|
@@ -157,12 +147,10 @@ test("verifyRequest() [draft-cavage]", async () => {
|
|
|
157
147
|
Digest: "SHA-256=YZyjeVQW5GwliJowASkteBJhFBTq3eQk/AMqRETc//A=",
|
|
158
148
|
Signature: "keyId=\"https://oeee.cafe/ap/users/3609fd4e-d51d-4db8-9f04-4189815864dd#main-key\",algorithm=\"hs2019\",created=\"1756126694\",expires=\"1756130294\",headers=\"(request-target) (created) (expires) content-type date digest host\",signature=\"XFb0jl2uMhE7RhbneE9sK9Zls2qZec8iy6+9O8UgDQeBGJThORFLjXKlps4QO1WAf1YSVB/i5aV6yF+h73Lm3ZiuAJDx1h+00iLsxoYuIw1CZvF0V2jELoo3sQ2/ZzqeoO6H5TbK7tKnU+ulFAPTuJgjIvPwYl11OMRouVS34NiaHP9Yx9pU813TLv37thG/hUKanyq8kk0IJWtDWteY/zxDvzoe7VOkBXVBHslMyrNAI/5JGulVQAQp/E61dJAhTHHIyGxkc/7iutWFZuqFXIiPJ9KR2OuKDj/B32hEzlsf5xH/CjqOJPIg1qMK8FzDiALCq6zjiKIBEnW8HQc/hQ==\""
|
|
159
149
|
}
|
|
160
|
-
})
|
|
161
|
-
const options2 = {
|
|
150
|
+
}), {
|
|
162
151
|
...options,
|
|
163
152
|
currentTime: Temporal.Instant.from("2025-08-25T12:58:14Z")
|
|
164
|
-
};
|
|
165
|
-
assert(await verifyRequest(request2, options2) != null);
|
|
153
|
+
}) != null);
|
|
166
154
|
});
|
|
167
155
|
test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
|
|
168
156
|
const currentTimestamp = 1709626184;
|
|
@@ -203,9 +191,7 @@ test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
|
|
|
203
191
|
const contentDigest = signed.headers.get("Content-Digest");
|
|
204
192
|
assertExists(contentDigest);
|
|
205
193
|
assert(contentDigest.startsWith("sha-256=:"), "Content-Digest should use RFC 9421 format");
|
|
206
|
-
|
|
207
|
-
const expectedDigestBase64 = encodeBase64(expectedDigest);
|
|
208
|
-
assertEquals(contentDigest, `sha-256=:${expectedDigestBase64}:`, "Content-Digest should have correct value");
|
|
194
|
+
assertEquals(contentDigest, `sha-256=:${encodeBase64(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(requestBody)))}:`, "Content-Digest should have correct value");
|
|
209
195
|
const signature = signed.headers.get("Signature");
|
|
210
196
|
assertExists(signature);
|
|
211
197
|
const sigFormat = /^sig1=:([A-Za-z0-9+/]+=*):/;
|
|
@@ -226,71 +212,58 @@ test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
|
|
|
226
212
|
assertEquals(parsedSig.sig1.byteLength > 0, true, "Signature value should be a non-empty Uint8Array");
|
|
227
213
|
const verifyHeaders = new Headers();
|
|
228
214
|
for (const [name, value] of signed.headers.entries()) if (name !== "Signature" && name !== "Signature-Input") verifyHeaders.set(name, value);
|
|
229
|
-
const
|
|
215
|
+
const reconstructedBase = createRfc9421SignatureBase(new Request(request.url, {
|
|
230
216
|
method: request.method,
|
|
231
217
|
headers: verifyHeaders
|
|
232
|
-
});
|
|
233
|
-
const reconstructedBase = createRfc9421SignatureBase(reconstructedRequest, parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
218
|
+
}), parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
234
219
|
const signatureBytes = new Uint8Array(parsedSig.sig1);
|
|
235
|
-
|
|
236
|
-
assert(signatureVerifies, "Manual verification of signature should succeed");
|
|
220
|
+
assert(await crypto.subtle.verify("RSASSA-PKCS1-v1_5", rsaPublicKey2.publicKey, signatureBytes, new TextEncoder().encode(reconstructedBase)), "Manual verification of signature should succeed");
|
|
237
221
|
});
|
|
238
222
|
test("createRfc9421SignatureBase()", () => {
|
|
239
|
-
|
|
223
|
+
assertEquals(createRfc9421SignatureBase(new Request("https://example.com/path?query=value", {
|
|
240
224
|
method: "POST",
|
|
241
225
|
headers: {
|
|
242
226
|
Host: "example.com",
|
|
243
227
|
Date: "Tue, 05 Mar 2024 07:49:44 GMT",
|
|
244
228
|
"Content-Type": "text/plain"
|
|
245
229
|
}
|
|
246
|
-
})
|
|
247
|
-
const components = [
|
|
230
|
+
}), [
|
|
248
231
|
"@method",
|
|
249
232
|
"@target-uri",
|
|
250
233
|
"host",
|
|
251
234
|
"date"
|
|
252
|
-
]
|
|
253
|
-
const created = 1709626184;
|
|
254
|
-
const signatureBase = createRfc9421SignatureBase(request, components, formatRfc9421SignatureParameters({
|
|
235
|
+
], formatRfc9421SignatureParameters({
|
|
255
236
|
algorithm: "rsa-v1_5-sha256",
|
|
256
237
|
keyId: new URL("https://example.com/key"),
|
|
257
|
-
created
|
|
258
|
-
}))
|
|
259
|
-
const expected = [
|
|
238
|
+
created: 1709626184
|
|
239
|
+
})), [
|
|
260
240
|
`"@method": POST`,
|
|
261
241
|
`"@target-uri": https://example.com/path?query=value`,
|
|
262
242
|
`"host": example.com`,
|
|
263
243
|
`"date": Tue, 05 Mar 2024 07:49:44 GMT`,
|
|
264
244
|
`"@signature-params": ("@method" "@target-uri" "host" "date");alg="rsa-v1_5-sha256";keyid="https://example.com/key";created=1709626184`
|
|
265
|
-
].join("\n");
|
|
266
|
-
assertEquals(signatureBase, expected);
|
|
245
|
+
].join("\n"));
|
|
267
246
|
});
|
|
268
247
|
test("formatRfc9421Signature()", () => {
|
|
269
|
-
const
|
|
248
|
+
const [signatureInput, signatureHeader] = formatRfc9421Signature(new Uint8Array([
|
|
270
249
|
1,
|
|
271
250
|
2,
|
|
272
251
|
3,
|
|
273
252
|
4
|
|
274
|
-
])
|
|
275
|
-
const keyId = new URL("https://example.com/key");
|
|
276
|
-
const algorithm = "rsa-v1_5-sha256";
|
|
277
|
-
const components = [
|
|
253
|
+
]), [
|
|
278
254
|
"@method",
|
|
279
255
|
"@target-uri",
|
|
280
256
|
"host"
|
|
281
|
-
]
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
keyId,
|
|
286
|
-
created
|
|
257
|
+
], formatRfc9421SignatureParameters({
|
|
258
|
+
algorithm: "rsa-v1_5-sha256",
|
|
259
|
+
keyId: new URL("https://example.com/key"),
|
|
260
|
+
created: 1709626184
|
|
287
261
|
}));
|
|
288
262
|
assertEquals(signatureInput, `sig1=("@method" "@target-uri" "host");alg="rsa-v1_5-sha256";keyid="https://example.com/key";created=1709626184`);
|
|
289
263
|
assertEquals(signatureHeader, `sig1=:AQIDBA==:`);
|
|
290
264
|
});
|
|
291
265
|
test("parseRfc9421SignatureInput()", () => {
|
|
292
|
-
const
|
|
293
|
-
const parsed = parseRfc9421SignatureInput(signatureInput);
|
|
266
|
+
const parsed = parseRfc9421SignatureInput(`sig1=("@method" "@target-uri" "host" "date");keyid="https://example.com/key";alg="rsa-v1_5-sha256";created=1709626184`);
|
|
294
267
|
assertEquals(parsed.sig1.keyId, "https://example.com/key");
|
|
295
268
|
assertEquals(parsed.sig1.alg, "rsa-v1_5-sha256");
|
|
296
269
|
assertEquals(parsed.sig1.created, 1709626184);
|
|
@@ -303,8 +276,7 @@ test("parseRfc9421SignatureInput()", () => {
|
|
|
303
276
|
assertEquals(parsed.sig1.parameters, "keyid=\"https://example.com/key\";alg=\"rsa-v1_5-sha256\";created=1709626184");
|
|
304
277
|
});
|
|
305
278
|
test("parseRfc9421Signature()", () => {
|
|
306
|
-
const
|
|
307
|
-
const parsed = parseRfc9421Signature(signature);
|
|
279
|
+
const parsed = parseRfc9421Signature(`sig1=:AQIDBA==:,sig2=:Zm9vYmFy:`);
|
|
308
280
|
assertExists(parsed.sig1);
|
|
309
281
|
assertExists(parsed.sig2);
|
|
310
282
|
const sig1Bytes = new Uint8Array(parsed.sig1);
|
|
@@ -313,37 +285,33 @@ test("parseRfc9421Signature()", () => {
|
|
|
313
285
|
assertEquals(sig1Bytes[1], 2);
|
|
314
286
|
assertEquals(sig1Bytes[2], 3);
|
|
315
287
|
assertEquals(sig1Bytes[3], 4);
|
|
316
|
-
|
|
317
|
-
assertEquals(sig2Text, "foobar");
|
|
288
|
+
assertEquals(new TextDecoder().decode(parsed.sig2), "foobar");
|
|
318
289
|
});
|
|
319
290
|
test("verifyRequest() [rfc9421] successful GET verification", async () => {
|
|
320
291
|
const currentTimestamp = 1709626184;
|
|
321
292
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
322
|
-
|
|
293
|
+
assertEquals(await verifyRequest(await signRequest(new Request("https://example.com/api/resource", {
|
|
323
294
|
method: "GET",
|
|
324
295
|
headers: {
|
|
325
296
|
"Accept": "application/json",
|
|
326
297
|
"Host": "example.com",
|
|
327
298
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
328
299
|
}
|
|
329
|
-
})
|
|
330
|
-
const signedRequest = await signRequest(validRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
300
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
331
301
|
spec: "rfc9421",
|
|
332
302
|
currentTime
|
|
333
|
-
})
|
|
334
|
-
const verifiedKey = await verifyRequest(signedRequest, {
|
|
303
|
+
}), {
|
|
335
304
|
contextLoader: mockDocumentLoader,
|
|
336
305
|
documentLoader: mockDocumentLoader,
|
|
337
306
|
spec: "rfc9421",
|
|
338
307
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
339
|
-
});
|
|
340
|
-
assertEquals(verifiedKey, rsaPublicKey2, "Valid signature should verify to the correct public key");
|
|
308
|
+
}), rsaPublicKey2, "Valid signature should verify to the correct public key");
|
|
341
309
|
});
|
|
342
310
|
test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
343
311
|
const currentTimestamp = 1709626184;
|
|
344
312
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
345
313
|
const postBody = "Test content for signature verification";
|
|
346
|
-
const
|
|
314
|
+
const signedPostRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
347
315
|
method: "POST",
|
|
348
316
|
body: postBody,
|
|
349
317
|
headers: {
|
|
@@ -352,8 +320,7 @@ test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
|
352
320
|
"Host": "example.com",
|
|
353
321
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
354
322
|
}
|
|
355
|
-
})
|
|
356
|
-
const signedPostRequest = await signRequest(postRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
323
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
357
324
|
spec: "rfc9421",
|
|
358
325
|
currentTime
|
|
359
326
|
});
|
|
@@ -375,61 +342,54 @@ test("verifyRequest() [rfc9421] manual POST verification", async () => {
|
|
|
375
342
|
assertExists(parsedSignature.sig1, "Should have a valid signature value");
|
|
376
343
|
assertEquals(parsedInput.sig1.keyId, "https://example.com/key2", "Signature should have the correct key ID");
|
|
377
344
|
assertEquals(parsedInput.sig1.created, currentTimestamp, "Signature should have the correct timestamp");
|
|
378
|
-
const
|
|
345
|
+
const signatureBase = createRfc9421SignatureBase(new Request("https://example.com/api/resource", {
|
|
379
346
|
method: "POST",
|
|
380
347
|
body: postBody,
|
|
381
348
|
headers: new Headers(signedPostRequest.headers)
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
const signatureVerified = await crypto.subtle.verify("RSASSA-PKCS1-v1_5", rsaPublicKey2.publicKey, parsedSignature.sig1.slice(), new TextEncoder().encode(signatureBase));
|
|
385
|
-
assert(signatureVerified, "Manual verification of POST signature should succeed");
|
|
349
|
+
}), parsedInput.sig1.components, parsedInput.sig1.parameters);
|
|
350
|
+
assert(await crypto.subtle.verify("RSASSA-PKCS1-v1_5", rsaPublicKey2.publicKey, parsedSignature.sig1.slice(), new TextEncoder().encode(signatureBase)), "Manual verification of POST signature should succeed");
|
|
386
351
|
});
|
|
387
352
|
test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
388
353
|
const currentTimestamp = 1709626184;
|
|
389
354
|
const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
|
|
390
|
-
const
|
|
355
|
+
const signedRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
391
356
|
method: "GET",
|
|
392
357
|
headers: {
|
|
393
358
|
"Accept": "application/json",
|
|
394
359
|
"Host": "example.com",
|
|
395
360
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
396
361
|
}
|
|
397
|
-
})
|
|
398
|
-
const signedRequest = await signRequest(validRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
362
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
399
363
|
spec: "rfc9421",
|
|
400
364
|
currentTime
|
|
401
365
|
});
|
|
402
366
|
const validSignatureInput = signedRequest.headers.get("Signature-Input") || "";
|
|
403
367
|
const validSignature = signedRequest.headers.get("Signature") || "";
|
|
404
|
-
|
|
368
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
405
369
|
method: "GET",
|
|
406
370
|
headers: new Headers({
|
|
407
371
|
"Accept": "application/json",
|
|
408
372
|
"Host": "example.com",
|
|
409
373
|
"Signature": validSignature
|
|
410
374
|
})
|
|
411
|
-
})
|
|
412
|
-
const missingInputResult = await verifyRequest(missingInputHeader, {
|
|
375
|
+
}), {
|
|
413
376
|
contextLoader: mockDocumentLoader,
|
|
414
377
|
documentLoader: mockDocumentLoader,
|
|
415
378
|
spec: "rfc9421"
|
|
416
|
-
});
|
|
417
|
-
assertEquals(
|
|
418
|
-
const missingSignatureHeader = new Request("https://example.com/api/resource", {
|
|
379
|
+
}), null, "Should fail verification when Signature-Input header is missing");
|
|
380
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
419
381
|
method: "GET",
|
|
420
382
|
headers: new Headers({
|
|
421
383
|
"Accept": "application/json",
|
|
422
384
|
"Host": "example.com",
|
|
423
385
|
"Signature-Input": validSignatureInput
|
|
424
386
|
})
|
|
425
|
-
})
|
|
426
|
-
const missingSignatureResult = await verifyRequest(missingSignatureHeader, {
|
|
387
|
+
}), {
|
|
427
388
|
contextLoader: mockDocumentLoader,
|
|
428
389
|
documentLoader: mockDocumentLoader,
|
|
429
390
|
spec: "rfc9421"
|
|
430
|
-
});
|
|
431
|
-
assertEquals(
|
|
432
|
-
const tamperedRequest = new Request("https://example.com/api/resource", {
|
|
391
|
+
}), null, "Should fail verification when Signature header is missing");
|
|
392
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
433
393
|
method: "GET",
|
|
434
394
|
headers: new Headers({
|
|
435
395
|
"Accept": "application/json",
|
|
@@ -438,14 +398,12 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
438
398
|
"Signature-Input": validSignatureInput,
|
|
439
399
|
"Signature": "sig1=:AAAAAA==:"
|
|
440
400
|
})
|
|
441
|
-
})
|
|
442
|
-
const tamperedResult = await verifyRequest(tamperedRequest, {
|
|
401
|
+
}), {
|
|
443
402
|
contextLoader: mockDocumentLoader,
|
|
444
403
|
documentLoader: mockDocumentLoader,
|
|
445
404
|
spec: "rfc9421"
|
|
446
|
-
});
|
|
447
|
-
assertEquals(
|
|
448
|
-
const expiredRequest = new Request("https://example.com/api/resource", {
|
|
405
|
+
}), null, "Should fail verification when signature is tampered");
|
|
406
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
449
407
|
method: "GET",
|
|
450
408
|
headers: new Headers({
|
|
451
409
|
"Accept": "application/json",
|
|
@@ -454,16 +412,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
454
412
|
"Signature-Input": validSignatureInput,
|
|
455
413
|
"Signature": validSignature
|
|
456
414
|
})
|
|
457
|
-
})
|
|
458
|
-
const expiredResult = await verifyRequest(expiredRequest, {
|
|
415
|
+
}), {
|
|
459
416
|
contextLoader: mockDocumentLoader,
|
|
460
417
|
documentLoader: mockDocumentLoader,
|
|
461
418
|
spec: "rfc9421",
|
|
462
419
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp + 2592e3) * 1e3)).toISOString()}`),
|
|
463
420
|
timeWindow: { hours: 1 }
|
|
464
|
-
});
|
|
465
|
-
assertEquals(
|
|
466
|
-
const futureRequest = new Request("https://example.com/api/resource", {
|
|
421
|
+
}), null, "Should fail verification when signature timestamp is too old");
|
|
422
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
467
423
|
method: "GET",
|
|
468
424
|
headers: new Headers({
|
|
469
425
|
"Accept": "application/json",
|
|
@@ -472,16 +428,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
472
428
|
"Signature-Input": validSignatureInput,
|
|
473
429
|
"Signature": validSignature
|
|
474
430
|
})
|
|
475
|
-
})
|
|
476
|
-
const futureResult = await verifyRequest(futureRequest, {
|
|
431
|
+
}), {
|
|
477
432
|
contextLoader: mockDocumentLoader,
|
|
478
433
|
documentLoader: mockDocumentLoader,
|
|
479
434
|
spec: "rfc9421",
|
|
480
435
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp - 2592e3) * 1e3)).toISOString()}`),
|
|
481
436
|
timeWindow: { hours: 1 }
|
|
482
|
-
});
|
|
483
|
-
assertEquals(
|
|
484
|
-
const timeCheckRequest = new Request("https://example.com/api/resource", {
|
|
437
|
+
}), null, "Should fail verification when signature timestamp is in the future");
|
|
438
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
485
439
|
method: "GET",
|
|
486
440
|
headers: new Headers({
|
|
487
441
|
"Accept": "application/json",
|
|
@@ -490,16 +444,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
490
444
|
"Signature-Input": validSignatureInput,
|
|
491
445
|
"Signature": validSignature
|
|
492
446
|
})
|
|
493
|
-
})
|
|
494
|
-
const timeDisabledResult = await verifyRequest(timeCheckRequest, {
|
|
447
|
+
}), {
|
|
495
448
|
contextLoader: mockDocumentLoader,
|
|
496
449
|
documentLoader: mockDocumentLoader,
|
|
497
450
|
spec: "rfc9421",
|
|
498
451
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date((currentTimestamp + 31536e3) * 1e3)).toISOString()}`),
|
|
499
452
|
timeWindow: false
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
const postRequest = new Request("https://example.com/api/resource", {
|
|
453
|
+
}), rsaPublicKey2, "Should verify signature when time checking is disabled");
|
|
454
|
+
const freshSignedPostRequest = await signRequest(new Request("https://example.com/api/resource", {
|
|
503
455
|
method: "POST",
|
|
504
456
|
body: "Test content for signature verification",
|
|
505
457
|
headers: {
|
|
@@ -508,15 +460,14 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
508
460
|
"Host": "example.com",
|
|
509
461
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT"
|
|
510
462
|
}
|
|
511
|
-
})
|
|
512
|
-
const freshSignedPostRequest = await signRequest(postRequest, rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
463
|
+
}), rsaPrivateKey2, new URL("https://example.com/key2"), {
|
|
513
464
|
spec: "rfc9421",
|
|
514
465
|
currentTime
|
|
515
466
|
});
|
|
516
467
|
const postSignatureInput = freshSignedPostRequest.headers.get("Signature-Input") || "";
|
|
517
468
|
const postSignature = freshSignedPostRequest.headers.get("Signature") || "";
|
|
518
469
|
const postContentDigest = freshSignedPostRequest.headers.get("Content-Digest") || "";
|
|
519
|
-
|
|
470
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
520
471
|
method: "POST",
|
|
521
472
|
body: "This content won't match the digest",
|
|
522
473
|
headers: new Headers({
|
|
@@ -527,22 +478,19 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
527
478
|
"Signature": postSignature,
|
|
528
479
|
"Content-Digest": postContentDigest
|
|
529
480
|
})
|
|
530
|
-
})
|
|
531
|
-
const tamperDigestResult = await verifyRequest(tamperDigestRequest, {
|
|
481
|
+
}), {
|
|
532
482
|
contextLoader: mockDocumentLoader,
|
|
533
483
|
documentLoader: mockDocumentLoader,
|
|
534
484
|
spec: "rfc9421",
|
|
535
485
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
536
|
-
});
|
|
537
|
-
assertEquals(tamperDigestResult, null, "Should fail verification with invalid Content-Digest");
|
|
486
|
+
}), null, "Should fail verification with invalid Content-Digest");
|
|
538
487
|
const testRequest = new Request("https://example.com/", { headers: new Headers({
|
|
539
488
|
"Date": "Tue, 05 Mar 2024 07:49:44 GMT",
|
|
540
489
|
"Host": "example.com",
|
|
541
490
|
"Signature-Input": `sig1=("@method" "@target-uri" "host" "date");keyid="https://example.com/key";alg="rsa-v1_5-sha256";created=1709626184`,
|
|
542
491
|
"Signature": `sig1=:YXNkZmprc2RmaGprc2RoZmprc2hkZmtqaHNkZg==:`
|
|
543
492
|
}) });
|
|
544
|
-
const
|
|
545
|
-
const parsedInput = parseRfc9421SignatureInput(signatureInput);
|
|
493
|
+
const parsedInput = parseRfc9421SignatureInput(testRequest.headers.get("Signature-Input") || "");
|
|
546
494
|
assertExists(parsedInput.sig1);
|
|
547
495
|
assertEquals(parsedInput.sig1.keyId, "https://example.com/key");
|
|
548
496
|
assertEquals(parsedInput.sig1.alg, "rsa-v1_5-sha256");
|
|
@@ -553,12 +501,10 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
553
501
|
"host",
|
|
554
502
|
"date"
|
|
555
503
|
]);
|
|
556
|
-
const
|
|
557
|
-
const parsedSig = parseRfc9421Signature(signature);
|
|
504
|
+
const parsedSig = parseRfc9421Signature(testRequest.headers.get("Signature") || "");
|
|
558
505
|
assertExists(parsedSig.sig1);
|
|
559
506
|
assert(new TextDecoder().decode(parsedSig.sig1).length > 0, "Signature base64 should decode to non-empty string");
|
|
560
|
-
const
|
|
561
|
-
const complexParsedInput = parseRfc9421SignatureInput(complexSignatureInput);
|
|
507
|
+
const complexParsedInput = parseRfc9421SignatureInput("sig1=(\"@method\" \"@target-uri\" \"host\" \"content-type\" \"value with \\\"quotes\\\" and spaces\");keyid=\"https://example.com/key with spaces\";alg=\"rsa-v1_5-sha256\";created=1709626184");
|
|
562
508
|
assertExists(complexParsedInput.sig1);
|
|
563
509
|
assertEquals(complexParsedInput.sig1.keyId, "https://example.com/key with spaces");
|
|
564
510
|
assertEquals(complexParsedInput.sig1.alg, "rsa-v1_5-sha256");
|
|
@@ -577,16 +523,13 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
577
523
|
assertEquals(multiParsedInput.sig2.alg, "rsa-pss-sha512");
|
|
578
524
|
const multiParsedSig = parseRfc9421Signature(multiSigRequest.headers.get("Signature") || "");
|
|
579
525
|
assertEquals(Object.keys(multiParsedSig).length, 2, "Should parse multiple signature values");
|
|
580
|
-
const
|
|
581
|
-
const parsedInvalidInput = parseRfc9421SignatureInput(invalidInputFormat);
|
|
526
|
+
const parsedInvalidInput = parseRfc9421SignatureInput("this is not a valid signature-input format");
|
|
582
527
|
assertEquals(Object.keys(parsedInvalidInput).length, 0, "Should handle invalid Signature-Input format");
|
|
583
|
-
const
|
|
584
|
-
const parsedInvalidSig = parseRfc9421Signature(invalidSigFormat);
|
|
528
|
+
const parsedInvalidSig = parseRfc9421Signature("this is not a valid signature format");
|
|
585
529
|
assertEquals(Object.keys(parsedInvalidSig).length, 0, "Should handle invalid Signature format");
|
|
586
|
-
const
|
|
587
|
-
const parsedInvalidBase64 = parseRfc9421Signature(invalidBase64Sig);
|
|
530
|
+
const parsedInvalidBase64 = parseRfc9421Signature("sig1=:!@#$%%^&*():");
|
|
588
531
|
assertEquals(Object.keys(parsedInvalidBase64).length, 0, "Should handle invalid base64 in signature");
|
|
589
|
-
|
|
532
|
+
assertEquals(await verifyRequest(new Request("https://example.com/api/resource", {
|
|
590
533
|
method: "GET",
|
|
591
534
|
headers: new Headers({
|
|
592
535
|
"Accept": "application/json",
|
|
@@ -595,14 +538,12 @@ test("verifyRequest() [rfc9421] error cases and edge cases", async () => {
|
|
|
595
538
|
"Signature-Input": `${validSignatureInput},sig2=("@method" "@target-uri" "host" "date");keyid="https://example.com/invalid-key";alg="rsa-v1_5-sha256";created=${currentTimestamp}`,
|
|
596
539
|
"Signature": `${validSignature},sig2=:AAAAAA==:`
|
|
597
540
|
})
|
|
598
|
-
})
|
|
599
|
-
const mixedResult = await verifyRequest(mixedRequest, {
|
|
541
|
+
}), {
|
|
600
542
|
contextLoader: mockDocumentLoader,
|
|
601
543
|
documentLoader: mockDocumentLoader,
|
|
602
544
|
spec: "rfc9421",
|
|
603
545
|
currentTime: Temporal.Instant.from(`${(/* @__PURE__ */ new Date(currentTimestamp * 1e3)).toISOString()}`)
|
|
604
|
-
});
|
|
605
|
-
assertEquals(mixedResult, rsaPublicKey2, "Should verify when at least one signature is valid");
|
|
546
|
+
}), rsaPublicKey2, "Should verify when at least one signature is valid");
|
|
606
547
|
});
|
|
607
548
|
test("verifyRequest() [rfc9421] test vector from Mastodon", async () => {
|
|
608
549
|
const signedRequest = new Request("https://www.example.com/activitypub/success", {
|
|
@@ -665,14 +606,13 @@ test("doubleKnock() function with successful first attempt", async () => {
|
|
|
665
606
|
const logFunction = (req) => {
|
|
666
607
|
loggedRequest = req;
|
|
667
608
|
};
|
|
668
|
-
|
|
609
|
+
assertEquals((await doubleKnock(request, {
|
|
669
610
|
keyId: rsaPublicKey2.id,
|
|
670
611
|
privateKey: rsaPrivateKey2
|
|
671
612
|
}, {
|
|
672
613
|
specDeterminer,
|
|
673
614
|
log: logFunction
|
|
674
|
-
});
|
|
675
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
615
|
+
})).status, 202, "Response status should be 202 Accepted");
|
|
676
616
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
677
617
|
assertEquals(firstRequestSpec, "rfc9421", "First attempt should use RFC 9421");
|
|
678
618
|
assertEquals(specDeterminer.usedSpec, "rfc9421", "Spec should be remembered");
|
|
@@ -713,11 +653,10 @@ test("doubleKnock() function with fallback to draft-cavage", async () => {
|
|
|
713
653
|
this.rememberedSpec = spec;
|
|
714
654
|
}
|
|
715
655
|
};
|
|
716
|
-
|
|
656
|
+
assertEquals((await doubleKnock(request, {
|
|
717
657
|
keyId: rsaPublicKey2.id,
|
|
718
658
|
privateKey: rsaPrivateKey2
|
|
719
|
-
}, { specDeterminer });
|
|
720
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
659
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
721
660
|
assertEquals(requestCount, 2, "Two requests should have been made");
|
|
722
661
|
assertEquals(firstSpec, "rfc9421", "First attempt should use RFC 9421");
|
|
723
662
|
assertEquals(secondSpec, "draft-cavage-http-signatures-12", "Second attempt should use draft-cavage");
|
|
@@ -739,16 +678,14 @@ test("doubleKnock() function with redirect handling", async () => {
|
|
|
739
678
|
responseCodes.push(202);
|
|
740
679
|
return new Response("", { status: 202 });
|
|
741
680
|
});
|
|
742
|
-
|
|
681
|
+
assertEquals((await doubleKnock(new Request("https://example.com/redirect-endpoint", {
|
|
743
682
|
method: "POST",
|
|
744
683
|
body: "Test message that will be redirected",
|
|
745
684
|
headers: { "Content-Type": "text/plain" }
|
|
746
|
-
})
|
|
747
|
-
const response = await doubleKnock(request, {
|
|
685
|
+
}), {
|
|
748
686
|
keyId: rsaPublicKey2.id,
|
|
749
687
|
privateKey: rsaPrivateKey2
|
|
750
|
-
});
|
|
751
|
-
assertEquals(response.status, 202, "Final response status should be 202 Accepted");
|
|
688
|
+
})).status, 202, "Final response status should be 202 Accepted");
|
|
752
689
|
assertEquals(requestedUrls.length, 2, "Two URLs should have been requested");
|
|
753
690
|
assertEquals(requestedUrls[0], "https://example.com/redirect-endpoint", "First request should be to redirect-endpoint");
|
|
754
691
|
assertEquals(requestedUrls[1], "https://example.com/final-endpoint", "Second request should be to final-endpoint");
|
|
@@ -767,16 +704,14 @@ test("doubleKnock() function with both specs rejected", async () => {
|
|
|
767
704
|
else attempts.push("unknown");
|
|
768
705
|
return new Response("Unauthorized", { status: 401 });
|
|
769
706
|
});
|
|
770
|
-
|
|
707
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-rejects-all", {
|
|
771
708
|
method: "POST",
|
|
772
709
|
body: "Test message that will be rejected regardless of signature format",
|
|
773
710
|
headers: { "Content-Type": "text/plain" }
|
|
774
|
-
})
|
|
775
|
-
const response = await doubleKnock(request, {
|
|
711
|
+
}), {
|
|
776
712
|
keyId: rsaPublicKey2.id,
|
|
777
713
|
privateKey: rsaPrivateKey2
|
|
778
|
-
});
|
|
779
|
-
assertEquals(response.status, 401, "Final response status should be 401 Unauthorized");
|
|
714
|
+
})).status, 401, "Final response status should be 401 Unauthorized");
|
|
780
715
|
assertEquals(requestCount, 2, "Two requests should have been made");
|
|
781
716
|
assertEquals(attempts.length, 2, "Two signature attempts should have been made");
|
|
782
717
|
assertEquals(attempts[0], "rfc9421", "First attempt should use RFC 9421");
|
|
@@ -800,16 +735,14 @@ test("doubleKnock() function with specDeterminer choosing draft-cavage first", a
|
|
|
800
735
|
},
|
|
801
736
|
rememberSpec(_origin, _spec) {}
|
|
802
737
|
};
|
|
803
|
-
|
|
738
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-accepts-any", {
|
|
804
739
|
method: "POST",
|
|
805
740
|
body: "Test message with draft-cavage preference",
|
|
806
741
|
headers: { "Content-Type": "text/plain" }
|
|
807
|
-
})
|
|
808
|
-
const response = await doubleKnock(request, {
|
|
742
|
+
}), {
|
|
809
743
|
keyId: rsaPublicKey2.id,
|
|
810
744
|
privateKey: rsaPrivateKey2
|
|
811
|
-
}, { specDeterminer });
|
|
812
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
745
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
813
746
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
814
747
|
assertEquals(firstSpec, "draft-cavage", "First attempt should use draft-cavage");
|
|
815
748
|
esm_default.hardReset();
|
|
@@ -920,25 +853,21 @@ test("doubleKnock() async specDeterminer test", async () => {
|
|
|
920
853
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
921
854
|
}
|
|
922
855
|
};
|
|
923
|
-
|
|
856
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-async-determiner", {
|
|
924
857
|
method: "POST",
|
|
925
858
|
body: "Test message with async spec determiner",
|
|
926
859
|
headers: { "Content-Type": "text/plain" }
|
|
927
|
-
})
|
|
928
|
-
const response = await doubleKnock(request, {
|
|
860
|
+
}), {
|
|
929
861
|
keyId: rsaPublicKey2.id,
|
|
930
862
|
privateKey: rsaPrivateKey2
|
|
931
|
-
}, { specDeterminer });
|
|
932
|
-
assertEquals(response.status, 202, "Response status should be 202 Accepted");
|
|
863
|
+
}, { specDeterminer })).status, 202, "Response status should be 202 Accepted");
|
|
933
864
|
assertEquals(requestCount, 1, "Only one request should have been made");
|
|
934
865
|
assertEquals(specUsed, "draft-cavage-http-signatures-12", "Should use spec from async determiner");
|
|
935
866
|
esm_default.hardReset();
|
|
936
867
|
});
|
|
937
868
|
test("timingSafeEqual()", async (t) => {
|
|
938
869
|
await t.step("should return true for equal empty arrays", () => {
|
|
939
|
-
|
|
940
|
-
const b = new Uint8Array([]);
|
|
941
|
-
assert(timingSafeEqual(a, b));
|
|
870
|
+
assert(timingSafeEqual(new Uint8Array([]), new Uint8Array([])));
|
|
942
871
|
});
|
|
943
872
|
await t.step("should return true for equal non-empty arrays", async (t2) => {
|
|
944
873
|
const testCases = [
|
|
@@ -1005,7 +934,7 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1005
934
|
assert(timingSafeEqual(arr, arr), "Array should be equal to itself by reference");
|
|
1006
935
|
});
|
|
1007
936
|
await t.step("should return false for arrays with same length but different content", async (t2) => {
|
|
1008
|
-
const
|
|
937
|
+
for (const tc of [
|
|
1009
938
|
{
|
|
1010
939
|
a: [
|
|
1011
940
|
1,
|
|
@@ -1063,13 +992,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1063
992
|
],
|
|
1064
993
|
name: "middle byte differs with edge values"
|
|
1065
994
|
}
|
|
1066
|
-
]
|
|
1067
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
995
|
+
]) await t2.step(tc.name, () => {
|
|
1068
996
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1069
997
|
});
|
|
1070
998
|
});
|
|
1071
999
|
await t.step("should return false for arrays with different lengths", async (t2) => {
|
|
1072
|
-
const
|
|
1000
|
+
for (const tc of [
|
|
1073
1001
|
{
|
|
1074
1002
|
a: [
|
|
1075
1003
|
1,
|
|
@@ -1106,13 +1034,12 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1106
1034
|
b: [],
|
|
1107
1035
|
name: "a non-empty, b empty"
|
|
1108
1036
|
}
|
|
1109
|
-
]
|
|
1110
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1037
|
+
]) await t2.step(tc.name, () => {
|
|
1111
1038
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1112
1039
|
});
|
|
1113
1040
|
});
|
|
1114
1041
|
await t.step("should return false where content matches up to shorter length", async (t2) => {
|
|
1115
|
-
const
|
|
1042
|
+
for (const tc of [
|
|
1116
1043
|
{
|
|
1117
1044
|
a: [1, 2],
|
|
1118
1045
|
b: [
|
|
@@ -1141,21 +1068,16 @@ test("timingSafeEqual()", async (t) => {
|
|
|
1141
1068
|
b: [0],
|
|
1142
1069
|
name: "two zeros vs single zero"
|
|
1143
1070
|
}
|
|
1144
|
-
]
|
|
1145
|
-
for (const tc of testCases) await t2.step(tc.name, () => {
|
|
1071
|
+
]) await t2.step(tc.name, () => {
|
|
1146
1072
|
assertFalse(timingSafeEqual(new Uint8Array(tc.a), new Uint8Array(tc.b)));
|
|
1147
1073
|
});
|
|
1148
1074
|
});
|
|
1149
1075
|
await t.step("should correctly handle comparisons involving padding bytes", async (t2) => {
|
|
1150
1076
|
await t2.step("a=[1], b=[1,0] (b longer with trailing zero)", () => {
|
|
1151
|
-
|
|
1152
|
-
const b1 = new Uint8Array([1, 0]);
|
|
1153
|
-
assertFalse(timingSafeEqual(a1, b1));
|
|
1077
|
+
assertFalse(timingSafeEqual(new Uint8Array([1]), new Uint8Array([1, 0])));
|
|
1154
1078
|
});
|
|
1155
1079
|
await t2.step("a=[1,0], b=[1] (a longer with trailing zero)", () => {
|
|
1156
|
-
|
|
1157
|
-
const b2 = new Uint8Array([1]);
|
|
1158
|
-
assertFalse(timingSafeEqual(a2, b2));
|
|
1080
|
+
assertFalse(timingSafeEqual(new Uint8Array([1, 0]), new Uint8Array([1])));
|
|
1159
1081
|
});
|
|
1160
1082
|
});
|
|
1161
1083
|
});
|
|
@@ -1172,20 +1094,18 @@ test("signRequest() [rfc9421] error handling for invalid signature base creation
|
|
|
1172
1094
|
assertExists(signedRequest.headers.get("Signature"));
|
|
1173
1095
|
});
|
|
1174
1096
|
test("verifyRequest() [rfc9421] error handling for invalid signature base creation", async () => {
|
|
1175
|
-
|
|
1097
|
+
assertEquals(await verifyRequest(new Request("https://example.com/test", {
|
|
1176
1098
|
method: "GET",
|
|
1177
1099
|
headers: {
|
|
1178
1100
|
"Accept": "application/json",
|
|
1179
1101
|
"Signature-Input": "sig1=(\"@unsupported\");alg=\"rsa-pss-sha256\";keyid=\"https://example.com/key2\";created=1234567890",
|
|
1180
1102
|
"Signature": "sig1=:invalid_signature_data:"
|
|
1181
1103
|
}
|
|
1182
|
-
})
|
|
1183
|
-
const result = await verifyRequest(request, {
|
|
1104
|
+
}), {
|
|
1184
1105
|
spec: "rfc9421",
|
|
1185
1106
|
documentLoader: mockDocumentLoader,
|
|
1186
1107
|
contextLoader: mockDocumentLoader
|
|
1187
|
-
});
|
|
1188
|
-
assertEquals(result, null, "Verification should fail gracefully for malformed signature inputs");
|
|
1108
|
+
}), null, "Verification should fail gracefully for malformed signature inputs");
|
|
1189
1109
|
});
|
|
1190
1110
|
test("doubleKnock() regression test for TypeError: unusable bug #294", async () => {
|
|
1191
1111
|
esm_default.spyGlobal();
|
|
@@ -1199,16 +1119,14 @@ test("doubleKnock() regression test for TypeError: unusable bug #294", async ()
|
|
|
1199
1119
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1200
1120
|
return new Response("Success", { status: 200 });
|
|
1201
1121
|
});
|
|
1202
|
-
|
|
1122
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1203
1123
|
method: "POST",
|
|
1204
1124
|
body: "Test activity content",
|
|
1205
1125
|
headers: { "Content-Type": "application/activity+json" }
|
|
1206
|
-
})
|
|
1207
|
-
const response = await doubleKnock(request, {
|
|
1126
|
+
}), {
|
|
1208
1127
|
keyId: rsaPublicKey2.id,
|
|
1209
1128
|
privateKey: rsaPrivateKey2
|
|
1210
|
-
});
|
|
1211
|
-
assertEquals(response.status, 200);
|
|
1129
|
+
})).status, 200);
|
|
1212
1130
|
assertEquals(requestCount, 2, "Should make 2 requests before redirect");
|
|
1213
1131
|
esm_default.hardReset();
|
|
1214
1132
|
});
|
|
@@ -1223,16 +1141,14 @@ test("doubleKnock() regression test for redirect handling bug", async () => {
|
|
|
1223
1141
|
esm_default.post("https://example.com/final-destination", () => {
|
|
1224
1142
|
return new Response("Success", { status: 200 });
|
|
1225
1143
|
});
|
|
1226
|
-
|
|
1144
|
+
assertEquals((await doubleKnock(new Request("https://example.com/inbox-retry-redirect", {
|
|
1227
1145
|
method: "POST",
|
|
1228
1146
|
body: "Test activity content",
|
|
1229
1147
|
headers: { "Content-Type": "application/activity+json" }
|
|
1230
|
-
})
|
|
1231
|
-
const response = await doubleKnock(request, {
|
|
1148
|
+
}), {
|
|
1232
1149
|
keyId: rsaPublicKey2.id,
|
|
1233
1150
|
privateKey: rsaPrivateKey2
|
|
1234
|
-
});
|
|
1235
|
-
assertEquals(response.status, 200);
|
|
1151
|
+
})).status, 200);
|
|
1236
1152
|
esm_default.hardReset();
|
|
1237
1153
|
});
|
|
1238
1154
|
test("signRequest() and verifyRequest() cancellation", {
|
|
@@ -1277,5 +1193,5 @@ test("signRequest() and verifyRequest() cancellation", {
|
|
|
1277
1193
|
});
|
|
1278
1194
|
esm_default.hardReset();
|
|
1279
1195
|
});
|
|
1280
|
-
|
|
1281
|
-
|
|
1196
|
+
//#endregion
|
|
1197
|
+
export {};
|