From 6872a929359eed6fc01fa3e1c838513a939a0b3b Mon Sep 17 00:00:00 2001 From: Zak Burke Date: Tue, 16 Jan 2024 14:12:49 -0500 Subject: [PATCH 1/4] CVE-2023-26159 require follow-redirects >= 1.15.4 (#1390) follow-redirects < 1.15.4 is vulnerable to CVE-2023-26159 --- yarn.lock | 1007 +++++++++++++++++++++++++++-------------------------- 1 file changed, 508 insertions(+), 499 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5a310d43a..74a8c8174 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21,9 +21,9 @@ "@jridgewell/trace-mapping" "^0.3.9" "@apollo/client@^3.2.1": - version "3.8.8" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.8.8.tgz#1a004b2e6de4af38668249a7d7790f6a3431e475" - integrity sha512-omjd9ryGDkadZrKW6l5ktUAdS4SNaFOccYQ4ZST0HLW83y8kQaSZOCTNlpkoBUK8cv6qP8+AxOKwLm2ho8qQ+Q== + version "3.8.9" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.8.9.tgz#0e4ac133eb04c63e618138c1ebf273d9f110a4d0" + integrity sha512-IcQDFEEPc9+PEQsxhxQvsoQ04BRarOzi/Ila5PcniRSDeKJWgY22dnp6+V1i1fWXRDVd1ybdvze4sFESDVQUCQ== dependencies: "@graphql-typed-document-node/core" "^3.1.1" "@wry/equality" "^0.5.6" @@ -53,26 +53,26 @@ "@babel/highlight" "^7.23.4" chalk "^2.4.2" -"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9", "@babel/compat-data@^7.23.3", "@babel/compat-data@^7.23.5": +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.3", "@babel/compat-data@^7.23.5": version "7.23.5" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.15.0", "@babel/core@^7.18.13", "@babel/core@^7.21.3", "@babel/core@^7.7.5", "@babel/core@^7.9.0": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.5.tgz#6e23f2acbcb77ad283c5ed141f824fd9f70101c7" - integrity sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g== + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.7.tgz#4d8016e06a14b5f92530a13ed0561730b5c6483f" + integrity sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.5" - "@babel/helper-compilation-targets" "^7.22.15" + "@babel/generator" "^7.23.6" + "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-module-transforms" "^7.23.3" - "@babel/helpers" "^7.23.5" - "@babel/parser" "^7.23.5" + "@babel/helpers" "^7.23.7" + "@babel/parser" "^7.23.6" "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.5" - "@babel/types" "^7.23.5" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" @@ -88,12 +88,12 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.23.5", "@babel/generator@^7.7.2": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.5.tgz#17d0a1ea6b62f351d281350a5f80b87a810c4755" - integrity sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA== +"@babel/generator@^7.23.6", "@babel/generator@^7.7.2": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== dependencies: - "@babel/types" "^7.23.5" + "@babel/types" "^7.23.6" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -112,21 +112,21 @@ dependencies: "@babel/types" "^7.22.15" -"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52" - integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw== +"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== dependencies: - "@babel/compat-data" "^7.22.9" - "@babel/helper-validator-option" "^7.22.15" - browserslist "^4.21.9" + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz#2a8792357008ae9ce8c0f2b78b9f646ac96b314b" - integrity sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A== +"@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.23.6", "@babel/helper-create-class-features-plugin@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.7.tgz#b2e6826e0e20d337143655198b79d58fdc9bd43d" + integrity sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.20" @@ -147,10 +147,10 @@ regexpu-core "^5.3.1" semver "^6.3.1" -"@babel/helper-define-polyfill-provider@^0.4.3": - version "0.4.3" - resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz#a71c10f7146d809f4a256c373f462d9bba8cf6ba" - integrity sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug== +"@babel/helper-define-polyfill-provider@^0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz#64df615451cb30e94b59a9696022cffac9a10088" + integrity sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA== dependencies: "@babel/helper-compilation-targets" "^7.22.6" "@babel/helper-plugin-utils" "^7.22.5" @@ -278,14 +278,14 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" -"@babel/helpers@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.5.tgz#52f522840df8f1a848d06ea6a79b79eefa72401e" - integrity sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg== +"@babel/helpers@^7.23.7": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.8.tgz#fc6b2d65b16847fd50adddbd4232c76378959e34" + integrity sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ== dependencies: "@babel/template" "^7.22.15" - "@babel/traverse" "^7.23.5" - "@babel/types" "^7.23.5" + "@babel/traverse" "^7.23.7" + "@babel/types" "^7.23.6" "@babel/highlight@^7.10.4", "@babel/highlight@^7.23.4": version "7.23.4" @@ -296,10 +296,10 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.5.tgz#37dee97c4752af148e1d38c34b856b2507660563" - integrity sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.15", "@babel/parser@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.6.tgz#ba1c9e512bda72a47e285ae42aff9d2a635a9e3b" + integrity sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": version "7.23.3" @@ -317,23 +317,21 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-optional-chaining" "^7.23.3" -"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz#20c60d4639d18f7da8602548512e9d3a4c8d7098" - integrity sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w== +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz#516462a95d10a9618f197d39ad291a9b47ae1d7b" + integrity sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw== dependencies: "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-proposal-decorators@^7.0.0": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.5.tgz#eeaa49d0dc9229aec4d23378653738cdc5a3ea0a" - integrity sha512-6IsY8jOeWibsengGlWIezp7cuZEFzNlAghFpzh9wiZwhQ42/hRcPnY/QV9HJoKTlujupinSlnQPiEy/u2C1ZfQ== + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.23.7.tgz#1d827902cbd3d9054e54fb2f2056cdd1eaa0e368" + integrity sha512-b1s5JyeMvqj7d9m9KhJNHKc18gEJiSyVzVX3bwbiPalQBQpuvfPh6lA9F7Kk/dWH0TIiXRpB9yicwijY6buPng== dependencies: - "@babel/helper-create-class-features-plugin" "^7.23.5" + "@babel/helper-create-class-features-plugin" "^7.23.7" "@babel/helper-plugin-utils" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.20" - "@babel/helper-split-export-declaration" "^7.22.6" "@babel/plugin-syntax-decorators" "^7.23.3" "@babel/plugin-proposal-function-sent@^7.0.0": @@ -541,10 +539,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/plugin-transform-async-generator-functions@^7.23.4": - version "7.23.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz#93ac8e3531f347fba519b4703f9ff2a75c6ae27a" - integrity sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw== +"@babel/plugin-transform-async-generator-functions@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz#3aa0b4f2fa3788b5226ef9346cf6d16ec61f99cd" + integrity sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA== dependencies: "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-plugin-utils" "^7.22.5" @@ -591,16 +589,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" -"@babel/plugin-transform-classes@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz#e7a75f815e0c534cc4c9a39c56636c84fc0d64f2" - integrity sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg== +"@babel/plugin-transform-classes@^7.23.8": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.8.tgz#d08ae096c240347badd68cdf1b6d1624a6435d92" + integrity sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.23.0" - "@babel/helper-optimise-call-expression" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.20" "@babel/helper-split-export-declaration" "^7.22.6" @@ -668,12 +665,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-flow" "^7.23.3" -"@babel/plugin-transform-for-of@^7.23.3": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz#afe115ff0fbce735e02868d41489093c63e15559" - integrity sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw== +"@babel/plugin-transform-for-of@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz#81c37e24171b37b370ba6aaffa7ac86bcb46f94e" + integrity sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw== dependencies: "@babel/helper-plugin-utils" "^7.22.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-function-name@^7.23.3": version "7.23.3" @@ -940,12 +938,12 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-typescript@^7.23.3": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.5.tgz#83da13ef62a1ebddf2872487527094b31c9adb84" - integrity sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA== + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.23.6.tgz#aa36a94e5da8d94339ae3a4e22d40ed287feb34c" + integrity sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.23.5" + "@babel/helper-create-class-features-plugin" "^7.23.6" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.23.3" @@ -981,17 +979,17 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.0.0", "@babel/preset-env@^7.20.2": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.23.5.tgz#350a3aedfa9f119ad045b068886457e895ba0ca1" - integrity sha512-0d/uxVD6tFGWXGDSfyMD1p2otoaKmu6+GD+NfAx0tMaH+dxORnp7T9TaVQ6mKyya7iBtCIVxHjWT7MuzzM9z+A== + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.23.8.tgz#7d6f8171ea7c221ecd28059e65ad37c20e441e3e" + integrity sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA== dependencies: "@babel/compat-data" "^7.23.5" - "@babel/helper-compilation-targets" "^7.22.15" + "@babel/helper-compilation-targets" "^7.23.6" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-option" "^7.23.5" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.23.3" "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.23.3" - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.23.3" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.23.7" "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" @@ -1012,13 +1010,13 @@ "@babel/plugin-syntax-top-level-await" "^7.14.5" "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" "@babel/plugin-transform-arrow-functions" "^7.23.3" - "@babel/plugin-transform-async-generator-functions" "^7.23.4" + "@babel/plugin-transform-async-generator-functions" "^7.23.7" "@babel/plugin-transform-async-to-generator" "^7.23.3" "@babel/plugin-transform-block-scoped-functions" "^7.23.3" "@babel/plugin-transform-block-scoping" "^7.23.4" "@babel/plugin-transform-class-properties" "^7.23.3" "@babel/plugin-transform-class-static-block" "^7.23.4" - "@babel/plugin-transform-classes" "^7.23.5" + "@babel/plugin-transform-classes" "^7.23.8" "@babel/plugin-transform-computed-properties" "^7.23.3" "@babel/plugin-transform-destructuring" "^7.23.3" "@babel/plugin-transform-dotall-regex" "^7.23.3" @@ -1026,7 +1024,7 @@ "@babel/plugin-transform-dynamic-import" "^7.23.4" "@babel/plugin-transform-exponentiation-operator" "^7.23.3" "@babel/plugin-transform-export-namespace-from" "^7.23.4" - "@babel/plugin-transform-for-of" "^7.23.3" + "@babel/plugin-transform-for-of" "^7.23.6" "@babel/plugin-transform-function-name" "^7.23.3" "@babel/plugin-transform-json-strings" "^7.23.4" "@babel/plugin-transform-literals" "^7.23.3" @@ -1060,9 +1058,9 @@ "@babel/plugin-transform-unicode-regex" "^7.23.3" "@babel/plugin-transform-unicode-sets-regex" "^7.23.3" "@babel/preset-modules" "0.1.6-no-external-plugins" - babel-plugin-polyfill-corejs2 "^0.4.6" - babel-plugin-polyfill-corejs3 "^0.8.5" - babel-plugin-polyfill-regenerator "^0.5.3" + babel-plugin-polyfill-corejs2 "^0.4.7" + babel-plugin-polyfill-corejs3 "^0.8.7" + babel-plugin-polyfill-regenerator "^0.5.4" core-js-compat "^3.31.0" semver "^6.3.1" @@ -1108,14 +1106,14 @@ "@babel/plugin-transform-typescript" "^7.23.3" "@babel/register@^7.0.0": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.22.15.tgz#c2c294a361d59f5fa7bcc8b97ef7319c32ecaec7" - integrity sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg== + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.23.7.tgz#485a5e7951939d21304cae4af1719fdb887bc038" + integrity sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ== dependencies: clone-deep "^4.0.1" find-cache-dir "^2.0.0" make-dir "^2.1.0" - pirates "^4.0.5" + pirates "^4.0.6" source-map-support "^0.5.16" "@babel/regjsgen@^0.8.0": @@ -1124,17 +1122,17 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime-corejs3@^7.10.2": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.23.5.tgz#818778beea4f23d40b77b5ad213894404c14f3f3" - integrity sha512-7+ziVclejQTLYhXl+Oi1f6gTGD1XDCeLa4R472TNGQxb08zbEJ0OdNoh5Piz+57Ltmui6xR88BXR4gS3/Toslw== + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.23.8.tgz#b8aa3d47570bdd08fed77fdfd69542118af0df26" + integrity sha512-2ZzmcDugdm0/YQKFVYsXiwUN7USPX8PM7cytpb4PFl87fM+qYPSvTZX//8tyeJB1j0YDmafBJEbl5f8NfLyuKw== dependencies: core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.5.tgz#11edb98f8aeec529b82b211028177679144242db" - integrity sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w== +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.23.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650" + integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw== dependencies: regenerator-runtime "^0.14.0" @@ -1147,26 +1145,26 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.23.5": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.5.tgz#f546bf9aba9ef2b042c0e00d245990c15508e7ec" - integrity sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w== +"@babel/traverse@^7.23.7": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.7.tgz#9a7bf285c928cb99b5ead19c3b1ce5b310c9c305" + integrity sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg== dependencies: "@babel/code-frame" "^7.23.5" - "@babel/generator" "^7.23.5" + "@babel/generator" "^7.23.6" "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-function-name" "^7.23.0" "@babel/helper-hoist-variables" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.5" - "@babel/types" "^7.23.5" - debug "^4.1.0" + "@babel/parser" "^7.23.6" + "@babel/types" "^7.23.6" + debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.5", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.23.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.5.tgz#48d730a00c95109fa4393352705954d74fb5b602" - integrity sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.6", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" + integrity sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg== dependencies: "@babel/helper-string-parser" "^7.23.4" "@babel/helper-validator-identifier" "^7.22.20" @@ -1256,24 +1254,59 @@ integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== "@csstools/cascade-layer-name-parser@^1.0.2": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.5.tgz#c4d276e32787651df0007af22c9fa70d9c9ca3c2" - integrity sha512-v/5ODKNBMfBl0us/WQjlfsvSlYxfZLhNMVIsuCPib2ulTwGKYbKJbwqw671+qH9Y4wvWVnu7LBChvml/wBKjFg== + version "1.0.7" + resolved "https://registry.yarnpkg.com/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-1.0.7.tgz#9cfc36de9716d219492eb0e5ee75348b2213a8fd" + integrity sha512-9J4aMRJ7A2WRjaRLvsMeWrL69FmEuijtiW1XlK/sG+V0UJiHVYUyvj9mY4WAXfU/hGIiGOgL8e0jJcRyaZTjDQ== -"@csstools/css-parser-algorithms@^2.2.0", "@csstools/css-parser-algorithms@^2.3.1": - version "2.3.2" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.2.tgz#1e0d581dbf4518cb3e939c3b863cb7180c8cedad" - integrity sha512-sLYGdAdEY2x7TSw9FtmdaTrh2wFtRJO5VMbBrA8tEqEod7GEggFmxTSK9XqExib3yMuYNcvcTdCZIP6ukdjAIA== +"@csstools/color-helpers@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-4.0.0.tgz#a1d6ffcefe5c1d389cbcca15f46da3cdaf241443" + integrity sha512-wjyXB22/h2OvxAr3jldPB7R7kjTUEzopvjitS8jWtyd8fN6xJ8vy1HnHu0ZNfEkqpBJgQ76Q+sBDshWcMvTa/w== -"@csstools/css-tokenizer@^2.1.1", "@csstools/css-tokenizer@^2.2.0": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.2.1.tgz#9dc431c9a5f61087af626e41ac2a79cce7bb253d" - integrity sha512-Zmsf2f/CaEPWEVgw29odOj+WEVoiJy9s9NOv5GgNY9mZ1CZ7394By6wONrONrTsnNDv6F9hR02nvFihrGVGHBg== +"@csstools/css-calc@^1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-1.1.6.tgz#2d4e16725c3f981f7c6e469c306bcb1f490e1082" + integrity sha512-YHPAuFg5iA4qZGzMzvrQwzkvJpesXXyIUyaONflQrjtHB+BcFFbgltJkIkb31dMGO4SE9iZFA4HYpdk7+hnYew== + +"@csstools/css-color-parser@^1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-1.5.1.tgz#bddf5513a7327c511c9e1ec682419dcad1f91869" + integrity sha512-x+SajGB2paGrTjPOUorGi8iCztF008YMKXTn+XzGVDBEIVJ/W1121pPerpneJYGOe1m6zWLPLnzOPaznmQxKFw== + dependencies: + "@csstools/color-helpers" "^4.0.0" + "@csstools/css-calc" "^1.1.6" + +"@csstools/css-parser-algorithms@^2.2.0", "@csstools/css-parser-algorithms@^2.3.1", "@csstools/css-parser-algorithms@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.5.0.tgz#0c03cd5418a9f404a05ff2ffcb1b69d04e8ec532" + integrity sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ== + +"@csstools/css-tokenizer@^2.1.1", "@csstools/css-tokenizer@^2.2.0", "@csstools/css-tokenizer@^2.2.3": + version "2.2.3" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.2.3.tgz#b099d543ea57b64f495915a095ead583866c50c6" + integrity sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg== "@csstools/media-query-list-parser@^2.1.1", "@csstools/media-query-list-parser@^2.1.4": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.5.tgz#94bc8b3c3fd7112a40b7bf0b483e91eba0654a0f" - integrity sha512-IxVBdYzR8pYe89JiyXQuYk4aVVoCPhMJkz6ElRwlVysjwURTsTk/bmY/z4FfeRE+CRBMlykPwXEVUg8lThv7AQ== + version "2.1.7" + resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.7.tgz#a4836e3dbd693081a30b32ce9c2a781e1be16788" + integrity sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ== + +"@csstools/postcss-progressive-custom-properties@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-3.0.3.tgz#8e0b61c204e80a3b4f82818f9738accb06894a06" + integrity sha512-WipTVh6JTMQfeIrzDV4wEPsV9NTzMK2jwXxyH6CGBktuWdivHnkioP/smp1x/0QDPQyx7NTS14RB+GV3zZZYEw== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-relative-color-syntax@^2.0.7": + version "2.0.9" + resolved "https://registry.yarnpkg.com/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-2.0.9.tgz#6a6c5361a6ec02459e024fa2b769aa52f392038e" + integrity sha512-2UoaRd2iIuzUGtYgteN5fJ0s+OfCiV7PvCnw8MCh3om8+SeVinfG8D5sqBOvImxFVfrp6k60XF5RFlH6oc//fg== + dependencies: + "@csstools/css-color-parser" "^1.5.1" + "@csstools/css-parser-algorithms" "^2.5.0" + "@csstools/css-tokenizer" "^2.2.3" + "@csstools/postcss-progressive-custom-properties" "^3.0.3" "@csstools/selector-specificity@^2.0.0": version "2.2.0" @@ -1281,9 +1314,9 @@ integrity sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw== "@csstools/selector-specificity@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.0.tgz#798622546b63847e82389e473fd67f2707d82247" - integrity sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g== + version "3.0.1" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-3.0.1.tgz#d84597fbc0f897240c12fc0a31e492b036c70e40" + integrity sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww== "@discoveryjs/json-ext@0.5.7": version "0.5.7" @@ -1428,9 +1461,9 @@ strip-json-comments "^3.1.1" "@folio/eslint-config-stripes@^7.0.0": - version "7.1.100000119" - resolved "https://repository.folio.org/repository/npm-folioci/@folio/eslint-config-stripes/-/eslint-config-stripes-7.1.100000119.tgz#9a6873736500c6ef4d655e2d32ddb4b8df576293" - integrity sha512-1xbLKV9J8udUhnwBf0Z2gJJNNZkNe2CQVTvXYkAOhBSdaymSnGRNzs6eRLG7+bBBXJ4xqtBvDUsev1Utv1jP/Q== + version "7.1.100000121" + resolved "https://repository.folio.org/repository/npm-folioci/@folio/eslint-config-stripes/-/eslint-config-stripes-7.1.100000121.tgz#8db7cd65cc453ed89089554b8e50346542ab905b" + integrity sha512-vvlWCzx5N4YZFjugDH1q86HqDZKKTMMSc2JqbhM/CHpuEq+4jADlydMfxCsmcz0vZ0vCdtpN/PDpHOtP3E9n4Q== dependencies: "@babel/core" "^7.18.13" "@babel/eslint-parser" "^7.15.0" @@ -1449,7 +1482,6 @@ eslint-plugin-react "7.30.1" eslint-plugin-react-hooks "4.6.0" eslint-plugin-testing-library "^5.6.0" - typescript "^4.9.4" webpack "^5.80.0" "@folio/jest-config-stripes@^2.0.0": @@ -1473,9 +1505,9 @@ jsdom "^22.1.0" "@folio/stripes-cli@^3.0.0": - version "3.1.100000382" - resolved "https://repository.folio.org/repository/npm-folioci/@folio/stripes-cli/-/stripes-cli-3.1.100000382.tgz#53ad9182eed5f6dc96b74ab44517d70e06de76fd" - integrity sha512-eYH1v+uHarBFkd+Jh7DynO4eD5kg8PZt2xY3qB71dgZb5fFvbrfv4PVV9s4JBVg98waGkkinCeQHwTCiVO/p6w== + version "3.1.100000390" + resolved "https://repository.folio.org/repository/npm-folioci/@folio/stripes-cli/-/stripes-cli-3.1.100000390.tgz#412a6cec58d921706156b9a7d39858177ac3d4b6" + integrity sha512-oewgzlepFuuJ5T5KJeZnzkIeZMOYaDEoB8jyvr3sXoLP8paw2CrDdyjfU0CzZO4qOlLcoFSNlWeAquHDhnSWig== dependencies: "@folio/stripes-testing" "^3.0.0" "@folio/stripes-webpack" "^5.0.0" @@ -1501,10 +1533,10 @@ just-pascal-case "^1.0.0" karma "^6.3.3" karma-browserstack-launcher "^1.3.0" - karma-chrome-launcher "^2.2.0" + karma-chrome-launcher "^3.1.0" karma-coverage-istanbul-reporter "^3.0.3" - karma-firefox-launcher "^1.1.0" - karma-junit-reporter "^1.2.0" + karma-firefox-launcher "^2.1.0" + karma-junit-reporter "^2.0.1" karma-mocha "^2.0.1" karma-mocha-reporter "^2.2.5" karma-webpack "^5.0.0" @@ -1528,9 +1560,9 @@ yargs "^17.3.1" "@folio/stripes-components@^12.0.0": - version "12.1.1000003633" - resolved "https://repository.folio.org/repository/npm-folioci/@folio/stripes-components/-/stripes-components-12.1.1000003633.tgz#3ac7b69bd6601887129fc1be87472835e28aba7f" - integrity sha512-4ok8l3nUH3OP7zhqonab8PgG2FrhU2nGOTFWxjCVLaIGdnKPGVxBMJHS/rGT8q/pnonAB8ejDERiX8s16J39gw== + version "12.1.1000003910" + resolved "https://repository.folio.org/repository/npm-folioci/@folio/stripes-components/-/stripes-components-12.1.1000003910.tgz#7068540cfa0b9e89a8eafe344b56ade934a6e6ce" + integrity sha512-CxPH1hOZlifC37MpGLAMa3vzrglLRetk5tQwAvriAkXmkFOl+Mo7NOamYSqbBnbL1lSJvta3yzd1eOEVyyUbnA== dependencies: "@folio/stripes-react-hotkeys" "^3.0.5" classnames "^2.2.5" @@ -1572,9 +1604,9 @@ integrity sha512-MDJaynBk+5c+Cu5EPnAbQBb+ja8zphSeOSTqAsaY3Ghh4YewYgNW4U243jdeaO85wYqMVK3R7Tqq2kJvRNS4xA== "@folio/stripes-react-hotkeys@^3.0.5": - version "3.0.100000026" - resolved "https://repository.folio.org/repository/npm-folioci/@folio/stripes-react-hotkeys/-/stripes-react-hotkeys-3.0.100000026.tgz#2fd1ae7fb248a9495661bbf1aa87b7c1a2ceadc9" - integrity sha512-4Qyq1rl8t2nQXafoqXF3m7pW9jeoYuWHx7XH3elEr/FxYgrbwyTJk2qdPPf8HL3dPq5Ra7b+L3R6tvUi7vXD9g== + version "3.0.100000034" + resolved "https://repository.folio.org/repository/npm-folioci/@folio/stripes-react-hotkeys/-/stripes-react-hotkeys-3.0.100000034.tgz#1cb28756933a83b45074d15f2e266ba90f07166b" + integrity sha512-1v/yHpe0GuuTpi6q1WLnbmG3sWI/ccxh/2ldAcU+xjDoKd4YdhfN/QKM/iug9xjoYmdfj3RyiTOwDo+E/P0hCA== dependencies: core-js "^3.0.0" keyboardjs "~2.5.1" @@ -1593,9 +1625,9 @@ minimist "^1.2.0" "@folio/stripes-webpack@^5.0.0": - version "5.1.100000568" - resolved "https://repository.folio.org/repository/npm-folioci/@folio/stripes-webpack/-/stripes-webpack-5.1.100000568.tgz#584f6277ecf346ae26cce0f545151d2461800403" - integrity sha512-5uqcpfWVU2HMbaxc2dCpXgCm4Af8g8pd/CrEseB+stMsYFSTGnaiw6OsgmppsAT4r8oQrHKfwZ47RYRn0KjYBA== + version "5.1.100000588" + resolved "https://repository.folio.org/repository/npm-folioci/@folio/stripes-webpack/-/stripes-webpack-5.1.100000588.tgz#a37aa93cba2f1b46fc146dc5878705f4b3c64d21" + integrity sha512-TF4hlPDpQ9KGPHHGN5flR5P/8+GIvxVB7HSfyoT+2yILqjrkOzAFAqLjIaFQfe6OGzeye5CrZclAaKd+dHAGug== dependencies: "@babel/core" "^7.9.0" "@babel/plugin-proposal-decorators" "^7.0.0" @@ -1613,9 +1645,10 @@ "@babel/preset-typescript" "^7.7.7" "@babel/register" "^7.0.0" "@cerner/duplicate-package-checker-webpack-plugin" "~2.1.0" + "@csstools/postcss-relative-color-syntax" "^2.0.7" "@pmmmwh/react-refresh-webpack-plugin" "^0.5.4" "@svgr/webpack" "^8.1.0" - add-asset-html-webpack-plugin "^5.0.2" + add-asset-html-webpack-plugin "^6.0.0" autoprefixer "^10.4.13" babel-loader "^9.1.3" babel-plugin-remove-jsx-attributes "^0.0.2" @@ -1629,13 +1662,13 @@ debug "^4.0.1" esbuild-loader "~3.0.1" express "^4.14.0" - favicons "^7.1.2" + favicons "7.1.4" favicons-webpack-plugin "^6.0.0" handlebars "^4.7.7" handlebars-loader "^1.7.1" html-webpack-plugin "^5.3.2" lodash "^4.17.21" - mini-css-extract-plugin "^1.6.2" + mini-css-extract-plugin "^2.7.6" node-object-hash "^1.2.0" postcss "^8.4.2" postcss-calc "^9.0.1" @@ -1658,7 +1691,7 @@ style-loader "^3.3.0" tapable "^1.0.0" ts-loader "^9.4.1" - typescript "^4.2.4" + typescript "^5.3.3" util-ex "^0.3.15" webpack-dev-middleware "^5.2.1" webpack-hot-middleware "^2.25.1" @@ -1666,12 +1699,12 @@ webpack-virtual-modules "^0.4.3" "@formatjs/cli-lib@^6.1.3": - version "6.3.3" - resolved "https://registry.yarnpkg.com/@formatjs/cli-lib/-/cli-lib-6.3.3.tgz#66e24748382c7fff1a1c526e0a870e9cb2ae618b" - integrity sha512-qQwrsghvp3Bg5rxP8xRNk2iloIvgClLhaBD30J4fxM6LucyGvYa5aTVNOy2lgqUKoGT51QjxFuzZxhx4YZdiqQ== + version "6.3.5" + resolved "https://registry.yarnpkg.com/@formatjs/cli-lib/-/cli-lib-6.3.5.tgz#c39a8180eec07fbe8154f628d2ae7e3928b62fee" + integrity sha512-gZMygUMVj8l0RBJYfFAmtCX3+pUaZyDWKNz8eoYZ8UUAXm4lX4m5b8fs+jKXTdwjzeBTH4TcW1rNlyZIxsDyhg== dependencies: - "@formatjs/icu-messageformat-parser" "2.7.3" - "@formatjs/ts-transformer" "3.13.9" + "@formatjs/icu-messageformat-parser" "2.7.5" + "@formatjs/ts-transformer" "3.13.11" "@types/estree" "^1.0.0" "@types/fs-extra" "^9.0.1" "@types/json-stable-stringify" "^1.0.32" @@ -1686,16 +1719,16 @@ typescript "5" "@formatjs/cli@^6.1.3": - version "6.2.4" - resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.2.4.tgz#4ab4e0376e2d5c9b8528a2eb9effed2a73100b3f" - integrity sha512-g1o9O143F5TGB55skib3fKbyjifPa9YoDcX9L07hVJocRKngCcu4JhKViyUSN55KGcN2ttfBomM+wihN6wtBSQ== + version "6.2.6" + resolved "https://registry.yarnpkg.com/@formatjs/cli/-/cli-6.2.6.tgz#dfb4b3c13ed0f15cd3243a1dd1f9572f333fce24" + integrity sha512-EoI2XKi/5D/VAxC5UiIKMg9tTmQMtJZs9rDKWFK9uhMZOIjxakTix5dUi2w50K3+3jqRRjk62V+DwdKLcapLQQ== -"@formatjs/ecma402-abstract@1.18.0": - version "1.18.0" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.0.tgz#e2120e7101020140661b58430a7ff4262705a2f2" - integrity sha512-PEVLoa3zBevWSCZzPIM/lvPCi8P5l4G+NXQMc/CjEiaCWgyHieUoo0nM7Bs0n/NbuQ6JpXEolivQ9pKSBHaDlA== +"@formatjs/ecma402-abstract@1.18.2": + version "1.18.2" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.18.2.tgz#bf103712a406874eb1e387858d5be2371ab3aa14" + integrity sha512-+QoPW4csYALsQIl8GbN14igZzDbuwzcpWrku9nyMXlaqAlwRBgl5V+p0vWMGFqHOw37czNXaP/lEk4wbLgcmtA== dependencies: - "@formatjs/intl-localematcher" "0.5.2" + "@formatjs/intl-localematcher" "0.5.4" tslib "^2.4.0" "@formatjs/fast-memoize@2.2.0": @@ -1705,67 +1738,67 @@ dependencies: tslib "^2.4.0" -"@formatjs/icu-messageformat-parser@2.7.3": - version "2.7.3" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.3.tgz#c8c95e7c9f8141bdb93bea0e92e4fcace19d3c9f" - integrity sha512-X/jy10V9S/vW+qlplqhMUxR8wErQ0mmIYSq4mrjpjDl9mbuGcCILcI1SUYkL5nlM4PJqpc0KOS0bFkkJNPxYRw== +"@formatjs/icu-messageformat-parser@2.7.5": + version "2.7.5" + resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.5.tgz#6c12c08544eafef874df13b30729daf7b4dbd089" + integrity sha512-zCB53HdGDibh6/2ISEN3TGsFQruQ6gGKMFV94qHNyVrs0tNO6ncKhV0vq0n3Ydz8ipIQ2GaYAvfCoimNOVvKqA== dependencies: - "@formatjs/ecma402-abstract" "1.18.0" - "@formatjs/icu-skeleton-parser" "1.7.0" + "@formatjs/ecma402-abstract" "1.18.2" + "@formatjs/icu-skeleton-parser" "1.7.2" tslib "^2.4.0" -"@formatjs/icu-skeleton-parser@1.7.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.0.tgz#796938d6d0ba8fc75bb9edee038d1350bfee32cb" - integrity sha512-Cfdo/fgbZzpN/jlN/ptQVe0lRHora+8ezrEeg2RfrNjyp+YStwBy7cqDY8k5/z2LzXg6O0AdzAV91XS0zIWv+A== +"@formatjs/icu-skeleton-parser@1.7.2": + version "1.7.2" + resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.7.2.tgz#ffbdd535c33249635ad0e54a34813194287a1567" + integrity sha512-nlIXVv280bjGW3ail5Np1+xgGKBnMhwQQIivgbk9xX0af8ESQO+y2VW9TOY7mCrs3WH786uVpZlLimXAlXH7SA== dependencies: - "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/ecma402-abstract" "1.18.2" tslib "^2.4.0" -"@formatjs/intl-displaynames@6.6.4": - version "6.6.4" - resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.4.tgz#dd9ca9bb2d1f4b140cc8814667bc830802621674" - integrity sha512-ET8KQ+L9Q0K8x1SnJQa4DNssUcbATlMopWqYvGGR8yAvw5qwAQc1fv+DshCoZNIE9pbcue0IGC4kWNAkWqlFag== +"@formatjs/intl-displaynames@6.6.6": + version "6.6.6" + resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-6.6.6.tgz#be9fea4d24f577bb1a9d0f3ef4f2dcdabb4fe42d" + integrity sha512-Dg5URSjx0uzF8VZXtHb6KYZ6LFEEhCbAbKoYChYHEOnMFTw/ZU3jIo/NrujzQD2EfKPgQzIq73LOUvW6Z/LpFA== dependencies: - "@formatjs/ecma402-abstract" "1.18.0" - "@formatjs/intl-localematcher" "0.5.2" + "@formatjs/ecma402-abstract" "1.18.2" + "@formatjs/intl-localematcher" "0.5.4" tslib "^2.4.0" -"@formatjs/intl-listformat@7.5.3": - version "7.5.3" - resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.3.tgz#c6f028471839cd1014760498f783fdfe011422d5" - integrity sha512-l7EOr0Yh1m8KagytukB90yw81uyzrM7amKFrgxXqphz4KeSIL0KPa68lPsdtZ+JmQB73GaDQRwLOwUKFZ1VZPQ== +"@formatjs/intl-listformat@7.5.5": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-7.5.5.tgz#e4c7d741f2201c65e7da71326726e61332c7161e" + integrity sha512-XoI52qrU6aBGJC9KJddqnacuBbPlb/bXFN+lIFVFhQ1RnFHpzuFrlFdjD9am2O7ZSYsyqzYRpkVcXeT1GHkwDQ== dependencies: - "@formatjs/ecma402-abstract" "1.18.0" - "@formatjs/intl-localematcher" "0.5.2" + "@formatjs/ecma402-abstract" "1.18.2" + "@formatjs/intl-localematcher" "0.5.4" tslib "^2.4.0" -"@formatjs/intl-localematcher@0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.2.tgz#5fcf029fd218905575e5080fa33facdcb623d532" - integrity sha512-txaaE2fiBMagLrR4jYhxzFO6wEdEG4TPMqrzBAcbr4HFUYzH/YC+lg6OIzKCHm8WgDdyQevxbAAV1OgcXctuGw== +"@formatjs/intl-localematcher@0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz#caa71f2e40d93e37d58be35cfffe57865f2b366f" + integrity sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g== dependencies: tslib "^2.4.0" -"@formatjs/intl@2.9.9": - version "2.9.9" - resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.9.tgz#866629b565e20dd7490f9e77ee41a00748913e8f" - integrity sha512-JI3CNgL2Zdg5lv9ncT2sYKqbAj2RGrCbdzaCckIxMPxn4QuHuOVvYUGmBAXVusBmfG/0sxLmMrnwnBioz+QKdA== +"@formatjs/intl@2.9.11": + version "2.9.11" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-2.9.11.tgz#2fdc8d4d7207477ae33ad0be735c1d5ee8751c4e" + integrity sha512-wJF5GKuopgeKy75e11JPjueC/XKAxrOndqVEZqg5zDrGuxALUD6Vo/x+oDTQwVZYf2zJnEzqZlUGtv5gSi/ChQ== dependencies: - "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/ecma402-abstract" "1.18.2" "@formatjs/fast-memoize" "2.2.0" - "@formatjs/icu-messageformat-parser" "2.7.3" - "@formatjs/intl-displaynames" "6.6.4" - "@formatjs/intl-listformat" "7.5.3" - intl-messageformat "10.5.8" + "@formatjs/icu-messageformat-parser" "2.7.5" + "@formatjs/intl-displaynames" "6.6.6" + "@formatjs/intl-listformat" "7.5.5" + intl-messageformat "10.5.10" tslib "^2.4.0" -"@formatjs/ts-transformer@3.13.9": - version "3.13.9" - resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-3.13.9.tgz#8b6f17b3b1abf9a76b5394ef2dfef0a27746127c" - integrity sha512-J3kmCHOwkc0Tru0ZnBHPVDIwHCcaNh/zB8ZU4VQFBH2jhaOku0drym/hjz+f9/PCKLutd3Q7alUi2+Z2VpBIng== +"@formatjs/ts-transformer@3.13.11": + version "3.13.11" + resolved "https://registry.yarnpkg.com/@formatjs/ts-transformer/-/ts-transformer-3.13.11.tgz#c3603ff59c4b3e54535a26b8e204f6ccdacd5a91" + integrity sha512-qOAGGUQC7GSMMaAFy2MAlC8REM58lUNi8W+VlRb57cDUpge46x2hP33Mzu5N1xmm6OCqDQTRXws5jEOcRI3C8Q== dependencies: - "@formatjs/icu-messageformat-parser" "2.7.3" + "@formatjs/icu-messageformat-parser" "2.7.5" "@types/json-stable-stringify" "^1.0.32" "@types/node" "14 || 16 || 17" chalk "^4.0.0" @@ -2037,10 +2070,10 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.21" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz#5dc1df7b3dc4a6209e503a924e1ca56097a2bb15" + integrity sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" @@ -2237,7 +2270,7 @@ "@pnpm/network.ca-file" "^1.0.1" config-chain "^1.1.11" -"@polka/url@^1.0.0-next.20": +"@polka/url@^1.0.0-next.24": version "1.0.0-next.24" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.24.tgz#58601079e11784d20f82d0585865bb42305c4df3" integrity sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ== @@ -2248,9 +2281,9 @@ integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== "@restart/hooks@^0.4.7": - version "0.4.11" - resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.11.tgz#8876ccce1d4ad2a4b793a31689d63df36cf56088" - integrity sha512-Ft/ncTULZN6ldGHiF/k5qt72O8JyRMOeg0tApvCni8LkoiEahO+z3TNxfXIVGy890YtWVDvJAl662dVJSJXvMw== + version "0.4.15" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.15.tgz#70990085c9b79d1f3e140db824b29218bdc2b5bb" + integrity sha512-cZFXYTxbpzYcieq/mBwSyXgqnGMHoBVh3J7MU0CCoIB4NRZxV9/TuwTBAaLMqpNhC3zTPMCgkQ5Ey07L02Xmcw== dependencies: dequal "^2.0.3" @@ -2411,9 +2444,9 @@ pretty-format "^27.0.2" "@testing-library/dom@^9.0.0", "@testing-library/dom@^9.3.0": - version "9.3.3" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.3.tgz#108c23a5b0ef51121c26ae92eb3179416b0434f5" - integrity sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw== + version "9.3.4" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.4.tgz#50696ec28376926fec0a1bf87d9dbac5e27f60ce" + integrity sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -2449,9 +2482,9 @@ "@types/react-dom" "^18.0.0" "@testing-library/user-event@^14.4.3": - version "14.5.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.1.tgz#27337d72046d5236b32fd977edee3f74c71d332f" - integrity sha512-UCcUKrUYGj7ClomOo2SpNVvx4/fkd/2BbIHDCle8A0ax+P3bU7yJwDBDrS6ZwdTMARWTGODX1hEsCcO+7beJjg== + version "14.5.2" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.5.2.tgz#db7257d727c891905947bd1c1a99da20e03c2ebd" + integrity sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ== "@tootallnate/once@2": version "2.0.0" @@ -2480,9 +2513,9 @@ "@types/babel__traverse" "*" "@types/babel__generator@*": - version "7.6.7" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.7.tgz#a7aebf15c7bc0eb9abd638bdb5c0b8700399c9d0" - integrity sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ== + version "7.6.8" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.8.tgz#f836c61f48b1346e7d2b0d93c6dacc5b9535d3ab" + integrity sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw== dependencies: "@babel/types" "^7.0.0" @@ -2495,9 +2528,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.20.4" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.4.tgz#ec2c06fed6549df8bc0eb4615b683749a4a92e1b" - integrity sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA== + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.5.tgz#7b7502be0aa80cc4ef22978846b983edaafcd4dd" + integrity sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ== dependencies: "@babel/types" "^7.20.7" @@ -2527,9 +2560,9 @@ "@types/estree" "*" "@types/eslint@*": - version "8.44.8" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.8.tgz#f4fe1dab9b3d3dd98082d4b9f80e59ab40f1261c" - integrity sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw== + version "8.56.2" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.2.tgz#1c72a9b794aa26a8b94ad26d5b9aa51c8a6384bb" + integrity sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -2591,9 +2624,9 @@ "@types/istanbul-lib-report" "*" "@types/jest@*": - version "29.5.10" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.10.tgz#a10fc5bab9e426081c12b2ef73d24d4f0c9b7f50" - integrity sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ== + version "29.5.11" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.11.tgz#0c13aa0da7d0929f078ab080ae5d4ced80fa2f2c" + integrity sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ== dependencies: expect "^29.0.0" pretty-format "^29.0.0" @@ -2628,9 +2661,9 @@ integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== "@types/node@*", "@types/node@>=10.0.0": - version "20.10.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.3.tgz#4900adcc7fc189d5af5bb41da8f543cea6962030" - integrity sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg== + version "20.11.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.4.tgz#c724a5d6723182af758b91b994209336f4439cb7" + integrity sha512-6I0fMH8Aoy2lOejL3s4LhyIYX34DPwY8bl5xlNjBvUEk8OHrcuzsFt+Ied4LvJihbtXPM+8zUqdydfIti86v9g== dependencies: undici-types "~5.26.4" @@ -2657,16 +2690,16 @@ parchment "^1.1.2" "@types/react-dom@^18.0.0": - version "18.2.17" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.17.tgz#375c55fab4ae671bd98448dcfa153268d01d6f64" - integrity sha512-rvrT/M7Df5eykWFxn6MYt5Pem/Dbyc1N8Y0S9Mrkw2WFCRiqUgw9P7ul2NpwsXCSM1DVdENzdG9J5SreqfAIWg== + version "18.2.18" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd" + integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw== dependencies: "@types/react" "*" "@types/react@*", "@types/react@16 || 17 || 18", "@types/react@>=16.9.11": - version "18.2.42" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7" - integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA== + version "18.2.48" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" + integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -3008,9 +3041,9 @@ acorn-jsx@^5.3.1: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.0.0, acorn-walk@^8.0.2: - version "8.3.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.0.tgz#2097665af50fd0cf7a2dfccd2b9368964e66540f" - integrity sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA== + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== acorn@^7.4.0: version "7.4.1" @@ -3018,14 +3051,14 @@ acorn@^7.4.0: integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.0.4, acorn@^8.1.0, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2: - version "8.11.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b" - integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w== + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== -add-asset-html-webpack-plugin@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/add-asset-html-webpack-plugin/-/add-asset-html-webpack-plugin-5.0.2.tgz#84b33492f5ecd8458b3926f0e308a61f0aca2569" - integrity sha512-tvizXk9TU8+0BMpygyLl3/ELnCkMfEFxBvrU1bbtgJMs+ZOZdXpAgM6zRljxtV6UsPIOVhcIUmZG9GYeaGiEJQ== +add-asset-html-webpack-plugin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/add-asset-html-webpack-plugin/-/add-asset-html-webpack-plugin-6.0.0.tgz#011bbafb70e5975e1916f61896f07a49281644d1" + integrity sha512-W5uv10hFCsYFwG6EM2pbV/RqLZT04jiUbR9uAaPSc2LC8XiGkEOfDtBagKzNxEKudK7i3EKWwPaFr8talsEb7g== dependencies: globby "^11.1.0" micromatch "^4.0.4" @@ -3358,9 +3391,9 @@ available-typed-arrays@^1.0.5: integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== axe-core@^4.4.3: - version "4.8.2" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.2.tgz#2f6f3cde40935825cf4465e3c1c9e77b240ff6ae" - integrity sha512-/dlp0fxyM3R8YW7MFzaHWXrf4zzbr0vaYb23VBFCl83R7nWNPg/yaQw2Dc8jzCMmDVLhSdzH8MjrsuIUuvX+6g== + version "4.8.3" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.8.3.tgz#205df863dd9917d5979e9435dab4d47692759051" + integrity sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw== axobject-query@^2.2.0: version "2.2.0" @@ -3425,29 +3458,29 @@ babel-plugin-module-resolver@^5.0.0: reselect "^4.1.7" resolve "^1.22.1" -babel-plugin-polyfill-corejs2@^0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz#b2df0251d8e99f229a8e60fc4efa9a68b41c8313" - integrity sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q== +babel-plugin-polyfill-corejs2@^0.4.7: + version "0.4.7" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz#679d1b94bf3360f7682e11f2cb2708828a24fe8c" + integrity sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ== dependencies: "@babel/compat-data" "^7.22.6" - "@babel/helper-define-polyfill-provider" "^0.4.3" + "@babel/helper-define-polyfill-provider" "^0.4.4" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.8.5: - version "0.8.6" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz#25c2d20002da91fe328ff89095c85a391d6856cf" - integrity sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ== +babel-plugin-polyfill-corejs3@^0.8.7: + version "0.8.7" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz#941855aa7fdaac06ed24c730a93450d2b2b76d04" + integrity sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA== dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.3" + "@babel/helper-define-polyfill-provider" "^0.4.4" core-js-compat "^3.33.1" -babel-plugin-polyfill-regenerator@^0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz#d4c49e4b44614607c13fb769bcd85c72bb26a4a5" - integrity sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw== +babel-plugin-polyfill-regenerator@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz#c6fc8eab610d3a11eb475391e52584bacfc020f4" + integrity sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg== dependencies: - "@babel/helper-define-polyfill-provider" "^0.4.3" + "@babel/helper-define-polyfill-provider" "^0.4.4" babel-plugin-remove-jsx-attributes@^0.0.2: version "0.0.2" @@ -3706,7 +3739,7 @@ browserify-sign@^4.0.0: readable-stream "^3.6.2" safe-buffer "^5.2.1" -browserslist@^4.14.5, browserslist@^4.21.10, browserslist@^4.21.9, browserslist@^4.22.2: +browserslist@^4.14.5, browserslist@^4.21.10, browserslist@^4.22.2: version "4.22.2" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== @@ -3848,9 +3881,9 @@ camelcase@^7.0.1: integrity sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw== caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565: - version "1.0.30001566" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz#61a8e17caf3752e3e426d4239c549ebbb37fef0d" - integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA== + version "1.0.30001577" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001577.tgz#a24991eb4ad67324ba8b96716340d53151f2f6f8" + integrity sha512-rs2ZygrG1PNXMfmncM0B5H1hndY5ZCC9b5TkFaVNfZ+AUlyqcMyVIQtc3fsezi0NUCk5XZfDf9WS6WxMxnfdrg== capital-case@^1.0.4: version "1.0.4" @@ -3862,9 +3895,9 @@ capital-case@^1.0.4: upper-case-first "^2.0.2" chai@^4.1.2: - version "4.3.10" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" - integrity sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g== + version "4.4.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.4.1.tgz#3603fa6eba35425b0f2ac91a009fe924106e50d1" + integrity sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g== dependencies: assertion-error "^1.1.0" check-error "^1.0.3" @@ -3983,9 +4016,9 @@ cjs-module-lexer@^1.0.0: integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== classnames@^2.2.5: - version "2.3.2" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== clean-css@^5.2.2: version "5.3.3" @@ -4283,21 +4316,21 @@ cookie@^0.4.0, cookie@~0.4.1: integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== core-js-compat@^3.31.0, core-js-compat@^3.33.1: - version "3.34.0" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.34.0.tgz#61a4931a13c52f8f08d924522bba65f8c94a5f17" - integrity sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA== + version "3.35.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.35.0.tgz#c149a3d1ab51e743bc1da61e39cb51f461a41873" + integrity sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw== dependencies: browserslist "^4.22.2" core-js-pure@^3.23.3, core-js-pure@^3.30.2: - version "3.34.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.34.0.tgz#981e462500708664c91b827a75b011f04a8134a0" - integrity sha512-pmhivkYXkymswFfbXsANmBAewXx86UBfmagP+w0wkK06kLsLlTK5oQmsURPivzMkIBQiYq2cjamcZExIwlFQIg== + version "3.35.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.35.0.tgz#4660033304a050215ae82e476bd2513a419fbb34" + integrity sha512-f+eRYmkou59uh7BPcyJ8MC76DiGhspj1KMxVIcF24tzP8NA9HVa1uC7BTW2tgx7E1QVCzDzsgp7kArrzhlz8Ew== core-js@^3.0.0, core-js@^3.26.1, core-js@^3.6.1: - version "3.34.0" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.34.0.tgz#5705e6ad5982678612e96987d05b27c6c7c274a5" - integrity sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag== + version "3.35.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.35.0.tgz#58e651688484f83c34196ca13f099574ee53d6b4" + integrity sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg== cors@~2.8.5: version "2.8.5" @@ -4307,7 +4340,7 @@ cors@~2.8.5: object-assign "^4" vary "^1" -cosmiconfig@^8.1.3, cosmiconfig@^8.2.0: +cosmiconfig@^8.1.3, cosmiconfig@^8.2.0, cosmiconfig@^8.3.5: version "8.3.6" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== @@ -4415,18 +4448,18 @@ css-functions-list@^3.2.1: integrity sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ== css-loader@^6.4.0: - version "6.8.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88" - integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g== + version "6.9.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.9.0.tgz#0cc2f14df94ed97c526c5ae42b6b13916d1d8d0e" + integrity sha512-3I5Nu4ytWlHvOP6zItjiHlefBNtrH+oehq8tnQa2kO305qpVyx9XNIT1CXIj5bgCJs7qICBCkgCYxQLKPANoLA== dependencies: icss-utils "^5.1.0" - postcss "^8.4.21" + postcss "^8.4.31" postcss-modules-extract-imports "^3.0.0" postcss-modules-local-by-default "^4.0.3" - postcss-modules-scope "^3.0.0" + postcss-modules-scope "^3.1.0" postcss-modules-values "^4.0.0" postcss-value-parser "^4.2.0" - semver "^7.3.8" + semver "^7.5.4" css-select@^4.1.3: version "4.3.0" @@ -4450,7 +4483,7 @@ css-select@^5.1.0: domutils "^3.0.1" nth-check "^2.0.1" -css-tree@^2.2.1, css-tree@^2.3.1: +css-tree@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== @@ -4486,7 +4519,7 @@ cssfontparser@^1.2.1: resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" integrity sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg== -csso@5.0.5: +csso@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== @@ -4518,9 +4551,9 @@ cssstyle@^3.0.0: rrweb-cssom "^0.6.0" csstype@^3.0.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== csv-loader@^3.0.3: version "3.0.5" @@ -4610,7 +4643,7 @@ debug@2.6.9, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: +debug@4, debug@4.3.4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4789,12 +4822,7 @@ deprecation@^2.0.0: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -dequal@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/dequal/-/dequal-1.0.0.tgz#41c6065e70de738541c82cdbedea5292277a017e" - integrity sha512-/Nd1EQbQbI9UbSHrMiKZjFLrXSnU328iQdZKPQf78XQI6C+gutkFUeoHpG5J08Ioa6HeRbRNFpSIclh1xyG0mw== - -dequal@^2.0.3: +dequal@2.0.3, dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -5025,9 +5053,9 @@ effection@^1.0.0: integrity sha512-ITAAnuI7sJqKhjvYlnm66NHaW7zrXzqgsGsjVaaqFHYDWPN8WylVj46xASfkrtwq5wmklbzy6tvRYaj2C4rDHw== electron-to-chromium@^1.4.601: - version "1.4.604" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.604.tgz#8a53d70adb8ebb7206df082aa58cffb48e44b501" - integrity sha512-JAJ4lyLJYudlgJPYJicimU9R+qZ/3iyeyQS99bfT7PWi7psYWeN84lPswTjpHxQueU34PKxM/IJzQS6poYlovQ== + version "1.4.632" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.632.tgz#df6253483b802eb83eee2fdc0e5067bd46f36f11" + integrity sha512-JGmudTwg7yxMYvR/gWbalqqQiyu7WTFv2Xu3vw4cJHXPFxNgAk0oy8UHaer8nLF4lZJa+rNoj6GsrKIVJTV6Tw== element-is-visible@^1.0.0: version "1.0.0" @@ -5831,9 +5859,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-xml-parser@^4.2.4: - version "4.3.2" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz#761e641260706d6e13251c4ef8e3f5694d4b0d79" - integrity sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg== + version "4.3.3" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz#aeaf5778392329f17168c40c51bcbfec8ff965be" + integrity sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg== dependencies: strnum "^1.0.5" @@ -5848,9 +5876,9 @@ fastparse@^1.0.0: integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + version "1.16.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.16.0.tgz#83b9a9375692db77a822df081edb6a9cf6839320" + integrity sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA== dependencies: reusify "^1.0.4" @@ -5865,7 +5893,7 @@ favicons-webpack-plugin@^6.0.0: optionalDependencies: html-webpack-plugin "^5.5.0" -favicons@^7.1.2: +favicons@7.1.4: version "7.1.4" resolved "https://registry.yarnpkg.com/favicons/-/favicons-7.1.4.tgz#bc0ed1a8d752f94a36912294681925e272d25ff0" integrity sha512-lnZpVgT7Fzz+DUjioKF1dMwLYlpqWCaB4gIksIfIKwtlhHO1Q7w23hERwHQjEsec+43iENwbTAPRDW3XvpLhbg== @@ -6061,9 +6089,9 @@ flexboxgrid2@^7.2.0: normalize.css "^7.0.0" follow-redirects@^1.0.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== for-each@^0.3.3: version "0.3.3" @@ -6119,13 +6147,6 @@ fromentries@^1.2.0: resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== -fs-access@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a" - integrity sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA== - dependencies: - null-check "^1.0.0" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -6341,9 +6362,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.6.0, globals@^13.9.0: - version "13.23.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02" - integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" @@ -6481,7 +6502,7 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.0: +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== @@ -6636,9 +6657,9 @@ html-tags@^3.3.1: integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== html-webpack-plugin@^5.3.2, html-webpack-plugin@^5.5.0: - version "5.5.3" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz#72270f4a78e222b5825b296e5e3e1328ad525a3e" - integrity sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg== + version "5.6.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz#50a8fa6709245608cb00e811eacecb8e0d7b7ea0" + integrity sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw== dependencies: "@types/html-minifier-terser" "^6.0.0" html-minifier-terser "^6.0.2" @@ -6874,14 +6895,14 @@ interpret@^1.4.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -intl-messageformat@10.5.8: - version "10.5.8" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.8.tgz#7184da425f360a53a5483a6194e16d666b011fc0" - integrity sha512-NRf0jpBWV0vd671G5b06wNofAN8tp7WWDogMZyaU8GUAsmbouyvgwmFJI7zLjfAMpm3zK+vSwRP3jzaoIcMbaA== +intl-messageformat@10.5.10: + version "10.5.10" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.5.10.tgz#638b7a9a4926a04f784dfe6e77104a0c537deb36" + integrity sha512-3yzwX6t/my9WRtNiqP05r+/UkpWxwstQiwaHAiuHmDRt7ykzWJ+nceOVjNLZYYWGiSltY+C+Likd8OIVkASepw== dependencies: - "@formatjs/ecma402-abstract" "1.18.0" + "@formatjs/ecma402-abstract" "1.18.2" "@formatjs/fast-memoize" "2.2.0" - "@formatjs/icu-messageformat-parser" "2.7.3" + "@formatjs/icu-messageformat-parser" "2.7.5" tslib "^2.4.0" invariant@^2.2.4: @@ -7178,7 +7199,7 @@ is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -is-wsl@^2.1.0: +is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== @@ -7742,7 +7763,7 @@ jest@^29.5.0: import-local "^3.0.2" jest-cli "^29.7.0" -jiti@^1.18.2: +jiti@^1.20.0: version "1.21.0" resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== @@ -7869,9 +7890,9 @@ json-stable-stringify-without-jsonify@^1.0.1: integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json-stable-stringify@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.0.tgz#43d39c7c8da34bfaf785a61a56808b0def9f747d" - integrity sha512-zfA+5SuwYN2VWqN1/5HZaDzQKLJHaBVMZIIM+wuYjdptkaQsqzDdqjqf+lZZJUuJq1aanHiY8LhH8LmH+qBYJA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz#52d4361b47d49168bcc4e564189a42e5a7439454" + integrity sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg== dependencies: call-bind "^1.0.5" isarray "^2.0.5" @@ -7959,12 +7980,11 @@ karma-browserstack-launcher@^1.3.0: browserstack-local "^1.3.7" q "~1.5.0" -karma-chrome-launcher@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf" - integrity sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w== +karma-chrome-launcher@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9" + integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q== dependencies: - fs-access "^1.0.0" which "^1.2.1" karma-coverage-istanbul-reporter@^3.0.3: @@ -7978,20 +7998,21 @@ karma-coverage-istanbul-reporter@^3.0.3: istanbul-reports "^3.0.2" minimatch "^3.0.4" -karma-firefox-launcher@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz#ebcbb1d1ddfada6be900eb8fae25bcf2dcdc8171" - integrity sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ== +karma-firefox-launcher@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/karma-firefox-launcher/-/karma-firefox-launcher-2.1.2.tgz#9a38cc783c579a50f3ed2a82b7386186385cfc2d" + integrity sha512-VV9xDQU1QIboTrjtGVD4NCfzIH7n01ZXqy/qpBhnOeGVOkG5JYPEm8kuSd7psHE6WouZaQ9Ool92g8LFweSNMA== dependencies: - is-wsl "^2.1.0" + is-wsl "^2.2.0" + which "^2.0.1" -karma-junit-reporter@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/karma-junit-reporter/-/karma-junit-reporter-1.2.0.tgz#4f9c40cedfb1a395f8aef876abf96189917c6396" - integrity sha512-FeuLOKlXNtJhIQK3oQASbO5QOib762CEHV8+L9wwTQpiZJgp7xKg3sNno66rL5bQPV2soG6fJdAFWqqnMJuh2w== +karma-junit-reporter@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/karma-junit-reporter/-/karma-junit-reporter-2.0.1.tgz#d34eef7f0b2fd064e0896954e8851a90cf14c8f3" + integrity sha512-VtcGfE0JE4OE1wn0LK8xxDKaTP7slN8DO3I+4xg6gAi1IoAHAXOJ1V9G/y45Xg6sxdxPOR3THCFtDlAfBo9Afw== dependencies: path-is-absolute "^1.0.0" - xmlbuilder "8.2.2" + xmlbuilder "12.0.0" karma-mocha-reporter@^2.2.5: version "2.2.5" @@ -8367,12 +8388,12 @@ map-stream@~0.1.0: integrity sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g== match-sorter@^6.0.2: - version "6.3.1" - resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" - integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== + version "6.3.3" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.3.tgz#95bd788b9d33e1a7f0b8d78434895e2e8ecf40da" + integrity sha512-sgiXxrRijEe0SzHKGX4HouCpfHRPnqteH42UdMEW7BlWy990ZkzcvonJGv4Uu9WE7Y1f8Yocm91+4qFPCbmNww== dependencies: - "@babel/runtime" "^7.12.5" - remove-accents "0.4.2" + "@babel/runtime" "^7.23.8" + remove-accents "0.5.0" mathml-tag-names@^2.1.3: version "2.1.3" @@ -8531,14 +8552,12 @@ min-indent@^1.0.0, min-indent@^1.0.1: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== -mini-css-extract-plugin@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz#83172b4fd812f8fc4a09d6f6d16f924f53990ca8" - integrity sha512-WhDvO3SjGm40oV5y26GjMJYjd2UMqrLAGKy5YS2/3QKJy2F7jgynuHTir/tgUUOiNQu5saXHdc8reo7YuhhT4Q== +mini-css-extract-plugin@^2.7.6: + version "2.7.7" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz#4acf02f362c641c38fb913bfcb7ca2fc4a7cf339" + integrity sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw== dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - webpack-sources "^1.1.0" + schema-utils "^4.0.0" minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" @@ -8701,16 +8720,16 @@ moment-range@^4.0.2: es6-symbol "^3.1.0" moment-timezone@^0.5.14: - version "0.5.43" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.43.tgz#3dd7f3d0c67f78c23cd1906b9b2137a09b3c4790" - integrity sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ== + version "0.5.44" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.44.tgz#a64a4e47b68a43deeab5ae4eb4f82da77cdf595f" + integrity sha512-nv3YpzI/8lkQn0U6RkLd+f0W/zy/JnoR5/EyPz/dNkPTBjA2jNLCVxaiQ8QpeLymhSZvX0wCL5s27NQWdOPwAw== dependencies: moment "^2.29.4" moment@^2.29.0, moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== moo-color@^1.0.2: version "1.0.3" @@ -8730,10 +8749,10 @@ morgan@^1.10.0: on-finished "~2.3.0" on-headers "~1.0.2" -mrmime@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" - integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== +mrmime@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4" + integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== ms@2.0.0: version "2.0.0" @@ -8811,9 +8830,9 @@ no-case@^3.0.4: tslib "^2.0.3" node-abi@^3.3.0: - version "3.52.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.52.0.tgz#ffba0a85f54e552547e5849015f40f9514d5ba7c" - integrity sha512-JJ98b02z16ILv7859irtXn4oUaFWADtvkzy2c0IAatNVX2Mc9Yoh8z6hZInn3QwvMEYhHuQloYi+TTQy67SIdQ== + version "3.54.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.54.0.tgz#f6386f7548817acac6434c6cba02999c9aebcc69" + integrity sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA== dependencies: semver "^7.3.5" @@ -8905,11 +8924,6 @@ nub@~0.0.0: resolved "https://registry.yarnpkg.com/nub/-/nub-0.0.0.tgz#b369bd32bdde66af59605c3b0520bc219dccc04f" integrity sha512-dK0Ss9C34R/vV0FfYJXuqDAqHlaW9fvWVufq9MmGF2umCuDbd5GRfRD9fpi/LiM0l4ZXf8IBB+RYmZExqCrf0w== -null-check@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd" - integrity sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw== - nwsapi@^2.2.2, nwsapi@^2.2.4: version "2.2.7" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30" @@ -9387,7 +9401,7 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pirates@^4.0.4, pirates@^4.0.5: +pirates@^4.0.4, pirates@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== @@ -9468,13 +9482,13 @@ postcss-import@^15.0.1: resolve "^1.1.7" postcss-loader@^7.2.4: - version "7.3.3" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.3.tgz#6da03e71a918ef49df1bb4be4c80401df8e249dd" - integrity sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA== + version "7.3.4" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-7.3.4.tgz#aed9b79ce4ed7e9e89e56199d25ad1ec8f606209" + integrity sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A== dependencies: - cosmiconfig "^8.2.0" - jiti "^1.18.2" - semver "^7.3.8" + cosmiconfig "^8.3.5" + jiti "^1.20.0" + semver "^7.5.4" postcss-media-minmax@^5.0.0: version "5.0.0" @@ -9500,10 +9514,10 @@ postcss-modules-local-by-default@^4.0.3: postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== +postcss-modules-scope@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz#fbfddfda93a31f310f1d152c2bb4d3f3c5592ee0" + integrity sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg== dependencies: postcss-selector-parser "^6.0.4" @@ -9533,9 +9547,9 @@ postcss-safe-parser@^6.0.0: integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ== postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.13" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" - integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== + version "6.0.15" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -9555,10 +9569,10 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.2, postcss@^8.4.21, postcss@^8.4.28: - version "8.4.32" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9" - integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw== +postcss@^8.4.2, postcss@^8.4.28, postcss@^8.4.31: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== dependencies: nanoid "^3.3.7" picocolors "^1.0.0" @@ -9895,19 +9909,19 @@ react-highlight-words@^0.20.0: prop-types "^15.5.8" react-intl@^6.4.4: - version "6.5.5" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.5.5.tgz#d2de7bfd79718a7e3d8031e2599e94e0c8638377" - integrity sha512-cI5UKvBh4tc1zxLIziHBYGMX3dhYWDEFlvUDVN6NfT2i96zTXz/zH2AmM8+2waqgOhwkFUzd+7kK1G9q7fiC2g== - dependencies: - "@formatjs/ecma402-abstract" "1.18.0" - "@formatjs/icu-messageformat-parser" "2.7.3" - "@formatjs/intl" "2.9.9" - "@formatjs/intl-displaynames" "6.6.4" - "@formatjs/intl-listformat" "7.5.3" + version "6.6.1" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-6.6.1.tgz#f3777fedaa8677af8bd04ccd6e1973f43f54d9f7" + integrity sha512-oaMRr5A5KVpaOgnF0vdLTmBPI8rLQPReujmXTeIt2jCmZOJ+bcgqY/idFLVr52Iu/Aia8pDMBFUnzRjcg8T5aw== + dependencies: + "@formatjs/ecma402-abstract" "1.18.2" + "@formatjs/icu-messageformat-parser" "2.7.5" + "@formatjs/intl" "2.9.11" + "@formatjs/intl-displaynames" "6.6.6" + "@formatjs/intl-listformat" "7.5.5" "@types/hoist-non-react-statics" "^3.3.1" "@types/react" "16 || 17 || 18" hoist-non-react-statics "^3.3.2" - intl-messageformat "10.5.8" + intl-messageformat "10.5.10" tslib "^2.4.0" react-is@^16.13.1, react-is@^16.3.2, react-is@^16.4.2, react-is@^16.6.0, react-is@^16.7.0: @@ -10153,9 +10167,9 @@ regenerator-runtime@^0.13.10, regenerator-runtime@^0.13.3: integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== regenerator-runtime@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" - integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== regenerator-transform@^0.15.2: version "0.15.2" @@ -10223,10 +10237,10 @@ release-zalgo@^1.0.0: dependencies: es6-error "^4.0.1" -remove-accents@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" - integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== +remove-accents@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" + integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== renderkid@^3.0.0: version "3.0.0" @@ -10442,12 +10456,12 @@ rxjs@^7.5.5: tslib "^2.1.0" safe-array-concat@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" - integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.0.tgz#8d0cae9cb806d6d1c06e08ab13d847293ebe0692" + integrity sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" + call-bind "^1.0.5" + get-intrinsic "^1.2.2" has-symbols "^1.0.3" isarray "^2.0.5" @@ -10462,12 +10476,12 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + version "1.0.2" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.2.tgz#3ba32bdb3ea35f940ee87e5087c60ee786c3f6c5" + integrity sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ== dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" + call-bind "^1.0.5" + get-intrinsic "^1.2.2" is-regex "^1.1.4" "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: @@ -10530,7 +10544,7 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.1.3, semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.0, semver@^7.5.3, semver@^7.5.4: +semver@^7.1.3, semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.0, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -10580,9 +10594,9 @@ serialize-javascript@^5.0.0: randombytes "^2.1.0" serialize-javascript@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" - integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" @@ -10602,14 +10616,15 @@ set-blocking@^2.0.0: integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== set-function-length@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" - integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== + version "1.2.0" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.0.tgz#2f81dc6c16c7059bda5ab7c82c11f03a515ed8e1" + integrity sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w== dependencies: define-data-property "^1.1.1" - get-intrinsic "^1.2.1" + function-bind "^1.1.2" + get-intrinsic "^1.2.2" gopd "^1.0.1" - has-property-descriptors "^1.0.0" + has-property-descriptors "^1.0.1" set-function-name@^2.0.0: version "2.0.1" @@ -10700,9 +10715,9 @@ simple-get@^4.0.0, simple-get@^4.0.1: simple-concat "^1.0.0" simple-git@^3.5.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.21.0.tgz#fb7b42749f53e7a53dfd213540d78b74e0aabe13" - integrity sha512-oTzw9248AF5bDTMk9MrxsRzEzivMlY+DWH0yWS4VYpMhNLhDWnN06pCtaUyPnqv/FpsdeNmRqmZugMABHRPdDA== + version "3.22.0" + resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.22.0.tgz#616d41c661e30f9c65778956317d422b1729a242" + integrity sha512-6JujwSs0ac82jkGjMHiCnTifvf1crOiY/+tfs/Pqih6iow7VrpNKRRNdWm6RtaXpvvv/JGNYhlUtLhGFqHF+Yw== dependencies: "@kwsites/file-exists" "^1.1.1" "@kwsites/promise-deferred" "^1.1.1" @@ -10716,12 +10731,12 @@ simple-swizzle@^0.2.2: is-arrayish "^0.3.1" sirv@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446" - integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA== + version "2.0.4" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0" + integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ== dependencies: - "@polka/url" "^1.0.0-next.20" - mrmime "^1.0.0" + "@polka/url" "^1.0.0-next.24" + mrmime "^2.0.0" totalist "^3.0.0" sisteransi@^1.0.5: @@ -10767,9 +10782,9 @@ socket.io-parser@~4.2.4: debug "~4.3.1" socket.io@^4.4.1: - version "4.7.2" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.2.tgz#22557d76c3f3ca48f82e73d68b7add36a22df002" - integrity sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw== + version "4.7.4" + resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.4.tgz#2401a2d7101e4bdc64da80b140d5d8b6a8c7738b" + integrity sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw== dependencies: accepts "~1.3.4" base64id "~2.0.0" @@ -10790,11 +10805,10 @@ source-map-js@^1.0.1, source-map-js@^1.0.2: integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== source-map-loader@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.1.tgz#72f00d05f5d1f90f80974eda781cbd7107c125f2" - integrity sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-4.0.2.tgz#1b378721b65adb21e874928a9fb22e8a340d06a5" + integrity sha512-oYwAqCuL0OZhBoSgmdrLa7mv9MjommVMiQIWgcztf+eS4+8BfcUee6nenFnDhKOhzAVnk5gpZdfnz1iiBv+5sg== dependencies: - abab "^2.0.6" iconv-lite "^0.6.3" source-map-js "^1.0.2" @@ -10945,9 +10959,9 @@ streamroller@^3.1.5: fs-extra "^8.1.0" streamx@^2.15.0: - version "2.15.5" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.5.tgz#87bcef4dc7f0b883f9359671203344a4e004c7f1" - integrity sha512-9thPGMkKC2GctCzyCUjME3yR03x2xNo0GPKGkRw2UMYN+gqWa9uqpyNWhmsNCutU5zHmkUum0LsCRQTXUgUCAg== + version "2.15.6" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.6.tgz#28bf36997ebc7bf6c08f9eba958735231b833887" + integrity sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw== dependencies: fast-fifo "^1.1.0" queue-tick "^1.0.1" @@ -11098,9 +11112,9 @@ strnum@^1.0.5: integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== style-loader@^3.3.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff" - integrity sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw== + version "3.3.4" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7" + integrity sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w== style-search@^0.1.0: version "0.1.0" @@ -11224,16 +11238,16 @@ svg-tags@^1.0.0: integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== svgo@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.0.5.tgz#0595cf3c762c4e5180713d7b92dc67deaf46c6a0" - integrity sha512-HQKHEo73pMNOlDlBcLgZRcHW2+1wo7bFYayAXkGN0l/2+h68KjlfZyMRhdhaGvoHV2eApOovl12zoFz42sT6rQ== + version "3.2.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.2.0.tgz#7a5dff2938d8c6096e00295c2390e8e652fa805d" + integrity sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ== dependencies: "@trysound/sax" "0.2.0" commander "^7.2.0" css-select "^5.1.0" - css-tree "^2.2.1" + css-tree "^2.3.1" css-what "^6.1.0" - csso "5.0.5" + csso "^5.0.5" picocolors "^1.0.0" symbol-observable@^4.0.0: @@ -11324,20 +11338,20 @@ temp-fs@^0.9.9: rimraf "~2.5.2" terser-webpack-plugin@^5.3.7: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== dependencies: - "@jridgewell/trace-mapping" "^0.3.17" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.8" + terser "^5.26.0" -terser@^5.10.0, terser@^5.16.8: - version "5.25.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.25.0.tgz#6579b4cca45b08bf0fdaa1a04605fd5860dfb2ac" - integrity sha512-we0I9SIsfvNUMP77zC9HG+MylwYYsGFSBG8qm+13oud2Yh+O104y614FRbyjpxys16jZwot72Fpi827YvGzuqg== +terser@^5.10.0, terser@^5.26.0: + version "5.26.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.26.0.tgz#ee9f05d929f4189a9c28a0feb889d96d50126fe1" + integrity sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -11467,9 +11481,9 @@ ts-loader@^9.4.1: source-map "^0.7.4" tsconfig-paths@^3.14.1: - version "3.14.2" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" - integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== dependencies: "@types/json5" "^0.0.29" json5 "^1.0.2" @@ -11601,15 +11615,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@5: - version "5.3.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" - integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== - -typescript@^4.2.4, typescript@^4.9.4: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@5, typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== ua-parser-js@^0.7.30: version "0.7.37" @@ -11782,11 +11791,11 @@ url-parse@^1.5.3: requires-port "^1.0.0" use-deep-compare@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/use-deep-compare/-/use-deep-compare-1.1.0.tgz#85580dde751f68400bf6ef7e043c7f986595cef8" - integrity sha512-6yY3zmKNCJ1jjIivfZMZMReZjr8e6iC6Uqtp701jvWJ6ejC/usXD+JjmslZDPJQgX8P4B1Oi5XSLHkOLeYSJsA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-deep-compare/-/use-deep-compare-1.2.1.tgz#076c9865868d98509e619206e27bc8eff0b8ed7c" + integrity sha512-JTnOZAr0fq1ix6CQ4XANoWIh03xAiMFlP/lVAYDdAOZwur6nqBSdATn1/Q9PLIGIW+C7xmFZBCcaA4KLDcQJtg== dependencies: - dequal "1.0.0" + dequal "2.0.3" use-sync-external-store@^1.0.0: version "1.2.0" @@ -11940,9 +11949,9 @@ webpack-dev-middleware@^5.2.1: schema-utils "^4.0.0" webpack-hot-middleware@^2.25.1: - version "2.25.4" - resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.4.tgz#d8bc9e9cb664fc3105c8e83d2b9ed436bee4e193" - integrity sha512-IRmTspuHM06aZh98OhBJtqLpeWFM8FXJS5UYpKYxCJzyFoyWj1w6VGFfomZU7OPA55dMLrQK0pRT1eQ3PACr4w== + version "2.26.0" + resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.26.0.tgz#0a103c9b2836c1f27d7f74bbe0e96c99c82d0265" + integrity sha512-okzjec5sAEy4t+7rzdT8eRyxsk0FDSmBPN2KwX4Qd+6+oQCfe5Ve07+u7cJvofgB+B4w5/4dO4Pz0jhhHyyPLQ== dependencies: ansi-html-community "0.0.8" html-entities "^2.1.0" @@ -11962,7 +11971,7 @@ webpack-remove-empty-scripts@^1.0.1: dependencies: ansis "1.5.2" -webpack-sources@^1.1.0, webpack-sources@^1.4.3: +webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -12187,9 +12196,9 @@ ws@^7.3.1: integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== ws@^8.11.0, ws@^8.13.0: - version "8.14.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== + version "8.16.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" + integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== ws@~8.11.0: version "8.11.0" @@ -12224,10 +12233,10 @@ xml@^1.0.1: resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== -xmlbuilder@8.2.2: - version "8.2.2" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773" - integrity sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw== +xmlbuilder@12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-12.0.0.tgz#e2ed675e06834a089ddfb84db96e2c2b03f78c1a" + integrity sha512-lMo8DJ8u6JRWp0/Y4XLa/atVDr75H9litKlb2E5j3V3MesoL50EBgZDWoLT3F/LztVnG67GjPXLZpqcky/UMnQ== xmlbuilder@^13.0.2: version "13.0.2" From 036135333b1bc7b7f52d518506b88eb7446f1358 Mon Sep 17 00:00:00 2001 From: Zak Burke Date: Tue, 16 Jan 2024 17:05:20 -0500 Subject: [PATCH 2/4] =?UTF-8?q?STCOR-671=20handle=20access-control=20via?= =?UTF-8?q?=20cookies=20and=20RTR=20=F0=9F=91=8B=20=F0=9F=94=84=20?= =?UTF-8?q?=F0=9F=94=92=20=F0=9F=98=85=20=20(#1376)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move auth tokens into HTTP-only cookies and implement refresh token rotation (STCOR-671) by overriding `global.fetch` and `global.XMLHttpRequest`, disabling login when cookies are disabled (STCOR-762). This functionality is implemented behind an opt-in feature-flag (STCOR-763). The core RTR logic here is largely the same as it was in PR #1346 😬 , though with several important differences: 1. No buggy service-worker 2. Handle `fetch` and `XMLHttpRequest` 3. Disable login if cookies are disabled 4. Everything is opt-in 😌 Not _everything_ in PR #1346 was awful, despite it being reverted in #1371 😬 . The fundamental difference here is that the global `fetch` and `XMLHttpRequest` functions have been replaced 🤢 by new implementations that handle RTR instead of intercepting such requests via the service-worker proxy. This is not lovely. It is not elegant. It isn't pretty in any way, but it is extremely simple and effective. Certainly, we want to migrate away from it, but given the options we thought it was best choice in the short-term. The options: 1. Centralized fix within stripes-core by fixing the service worker. Let's be honest, I didn't get it right in #1346 and then couldn't get it right in #1361 or #1363 or #1366 or #1369. Why would anybody possibly believe that I could get it right now? 2. Decentralized fix: handle this in each UI-* repository by exporting a new function from stripes and refactoring each UI repo to leverage the new code. Probably not a big refactor, but not a small effort. 3. Centralized fix within stripes-core by overwriting `global.fetch`. Gross, but effective, and long term we can make this a decentralized approach by exporting our new `fetch` function, doing the refactor described in 2 (above), and removing the global-overwrite once all the refactoring is done. In summary: * Replaces #1340. It was gross and I really don't want to talk about it. Let us never mention it again. * Replaces #1346. It was a terrible, horrible, no good, very bad PR. Alexander hated that PR more than lima beans. Additional requirements: * Requires https://github.com/folio-org/stripes-connect/pull/223 * Requires https://github.com/folio-org/stripes-smart-components/pull/1397 * Requires https://github.com/folio-org/stripes-webpack/pull/125 Refs STCOR-671, FOLIO-3627 --- CHANGELOG.md | 2 + index.js | 3 + package.json | 1 + src/App.js | 5 - src/RootWithIntl.js | 5 +- src/Stripes.js | 2 + src/components/Login/Login.js | 17 +- src/components/MainNav/MainNav.js | 13 +- src/components/Root/Errors.js | 27 ++ src/components/Root/Events.js | 5 + src/components/Root/FFetch.js | 271 ++++++++++++++++++ src/components/Root/FFetch.test.js | 254 +++++++++++++++++ src/components/Root/FXHR.js | 30 ++ src/components/Root/FXHR.test.js | 50 ++++ src/components/Root/Root.js | 21 +- src/components/Root/token-util.js | 314 +++++++++++++++++++++ src/components/Root/token-util.test.js | 312 ++++++++++++++++++++ src/createApolloClient.js | 9 +- src/discoverServices.js | 10 +- src/loginServices.js | 273 ++++++++++++++---- src/loginServices.test.js | 109 +++++-- src/mainActions.js | 4 - src/okapiActions.js | 16 ++ src/okapiActions.test.js | 15 + src/okapiReducer.js | 8 +- src/okapiReducer.test.js | 8 +- src/queries/useConfigurations.test.js | 14 + src/queries/useOkapiEnv.test.js | 14 + src/service-worker.js | 2 +- src/serviceWorkerRegistration.js | 5 +- src/useOkapiKy.js | 8 +- src/useOkapiKy.test.js | 3 - src/withOkapiKy.js | 19 +- test/bigtest/helpers/setup-application.js | 11 +- test/bigtest/network/config.js | 29 +- test/bigtest/tests/session-timeout-test.js | 2 +- test/jest/setupFiles.js | 6 +- translations/stripes-core/en.json | 1 + translations/stripes-core/en_GB.json | 1 + translations/stripes-core/en_SE.json | 1 + translations/stripes-core/en_US.json | 1 + yarn.lock | 24 +- 42 files changed, 1785 insertions(+), 140 deletions(-) create mode 100644 src/components/Root/Errors.js create mode 100644 src/components/Root/Events.js create mode 100644 src/components/Root/FFetch.js create mode 100644 src/components/Root/FFetch.test.js create mode 100644 src/components/Root/FXHR.js create mode 100644 src/components/Root/FXHR.test.js create mode 100644 src/components/Root/token-util.js create mode 100644 src/components/Root/token-util.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index cb1c1f3d0..b56076101 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ * Fix duplicated "FOLIO" in document title in some cases. Refs STCOR-767. * Refactor away from `color()` function. Refs STCOR-768. * Export `getEventHandler` to be able to create events in other modules. Refs STCOR-770. +* Opt-in: handle access-control via cookies. Refs STCOR-671. +* Opt-in: disable login when cookies are disabled. Refs STCOR-762. ## [10.0.0](https://github.com/folio-org/stripes-core/tree/v10.0.0) (2023-10-11) [Full Changelog](https://github.com/folio-org/stripes-core/compare/v9.0.0...v10.0.0) diff --git a/index.js b/index.js index 4018294d2..5109591d8 100644 --- a/index.js +++ b/index.js @@ -46,6 +46,9 @@ export * from './src/consortiaServices'; export { default as queryLimit } from './src/queryLimit'; export { default as init } from './src/init'; +/* localforage wrappers hide the session key */ +export { getOkapiSession, getTokenExpiry, setTokenExpiry } from './src/loginServices'; + export { registerServiceWorker, unregisterServiceWorker } from './src/serviceWorkerRegistration'; export { getEventHandler } from './src/handlerService'; diff --git a/package.json b/package.json index 1c171d274..be86e3e0c 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@formatjs/cli": "^6.1.3", "chai": "^4.1.2", "eslint": "^7.32.0", + "jest-fetch-mock": "^3.0.3", "miragejs": "^0.1.32", "moment": "^2.29.0", "react": "^18.2.0", diff --git a/src/App.js b/src/App.js index 465603a8a..437037235 100644 --- a/src/App.js +++ b/src/App.js @@ -9,7 +9,6 @@ import configureLogger from './configureLogger'; import configureStore from './configureStore'; import gatherActions from './gatherActions'; import { destroyStore } from './mainActions'; -import { unregisterServiceWorker } from './serviceWorkerRegistration'; import Root from './components/Root'; @@ -31,10 +30,6 @@ export default class StripesCore extends Component { this.epics = configureEpics(connectErrorEpic); this.store = configureStore(initialState, this.logger, this.epics); this.actionNames = gatherActions(); - - // unregister any zombie service workers left over from RTR work - // prior to disabling RTR in PR #1371 - unregisterServiceWorker(); } componentWillUnmount() { diff --git a/src/RootWithIntl.js b/src/RootWithIntl.js index 1f67251b5..a507fff5a 100644 --- a/src/RootWithIntl.js +++ b/src/RootWithIntl.js @@ -47,12 +47,14 @@ class RootWithIntl extends React.Component { clone: PropTypes.func.isRequired, }).isRequired, token: PropTypes.string, + isAuthenticated: PropTypes.bool, disableAuth: PropTypes.bool.isRequired, history: PropTypes.shape({}), }; static defaultProps = { token: '', + isAuthenticated: false, history: {}, }; @@ -67,6 +69,7 @@ class RootWithIntl extends React.Component { render() { const { token, + isAuthenticated, disableAuth, history, } = this.props; @@ -85,7 +88,7 @@ class RootWithIntl extends React.Component { > - { token || disableAuth ? + { isAuthenticated || token || disableAuth ? <> diff --git a/src/Stripes.js b/src/Stripes.js index 560397a39..3d7413354 100644 --- a/src/Stripes.js +++ b/src/Stripes.js @@ -50,6 +50,7 @@ export const stripesShape = PropTypes.shape({ okapiReady: PropTypes.bool, tenant: PropTypes.string.isRequired, token: PropTypes.string, + isAuthenticated: PropTypes.bool, translations: PropTypes.object, url: PropTypes.string.isRequired, withoutOkapi: PropTypes.bool, @@ -57,6 +58,7 @@ export const stripesShape = PropTypes.shape({ plugins: PropTypes.object, setBindings: PropTypes.func.isRequired, setCurrency: PropTypes.func.isRequired, + setIsAuthenticated: PropTypes.func.isRequired, setLocale: PropTypes.func.isRequired, setSinglePlugin: PropTypes.func.isRequired, setTimezone: PropTypes.func.isRequired, diff --git a/src/components/Login/Login.js b/src/components/Login/Login.js index 9034bf218..48bad369a 100644 --- a/src/components/Login/Login.js +++ b/src/components/Login/Login.js @@ -41,6 +41,20 @@ class Login extends Component { onSubmit, } = this.props; + const cookieMessage = navigator.cookieEnabled ? + '' : + ( + + + + + + + ); + return (
{ const { username } = values; const submissionStatus = submitting || submitSucceeded; - const buttonDisabled = submissionStatus || !(username); + const buttonDisabled = submissionStatus || !(username) || !(navigator.cookieEnabled); const buttonLabel = submissionStatus ? 'loggingIn' : 'login'; return (
@@ -82,6 +96,7 @@ class Login extends Component { + { cookieMessage }
diff --git a/src/components/MainNav/MainNav.js b/src/components/MainNav/MainNav.js index 86a886fa6..d1b7a6e07 100644 --- a/src/components/MainNav/MainNav.js +++ b/src/components/MainNav/MainNav.js @@ -4,7 +4,6 @@ import { isEqual, find } from 'lodash'; import { compose } from 'redux'; import { injectIntl } from 'react-intl'; import { withRouter } from 'react-router'; -import localforage from 'localforage'; import { branding, config } from 'stripes-config'; @@ -12,9 +11,7 @@ import { Icon } from '@folio/stripes-components'; import { withModules } from '../Modules'; import { LastVisitedContext } from '../LastVisited'; -import { clearOkapiToken, clearCurrentUser } from '../../okapiActions'; -import { resetStore } from '../../mainActions'; -import { getLocale } from '../../loginServices'; +import { getLocale, logout as sessionLogout } from '../../loginServices'; import { updateQueryResource, getLocationQuery, @@ -123,12 +120,8 @@ class MainNav extends Component { returnToLogin() { const { okapi } = this.store.getState(); - return getLocale(okapi.url, this.store, okapi.tenant).then(() => { - this.store.dispatch(clearOkapiToken()); - this.store.dispatch(clearCurrentUser()); - this.store.dispatch(resetStore()); - localforage.removeItem('okapiSess'); - }); + return getLocale(okapi.url, this.store, okapi.tenant) + .then(sessionLogout(okapi.url, this.store)); } // return the user to the login screen, but after logging in they will be brought to the default screen. diff --git a/src/components/Root/Errors.js b/src/components/Root/Errors.js new file mode 100644 index 000000000..5d4017596 --- /dev/null +++ b/src/components/Root/Errors.js @@ -0,0 +1,27 @@ +/* eslint-disable import/prefer-default-export */ +/* eslint-disable max-classes-per-file */ + +/** + * RTRError + * Error occured during rotation + */ +export class RTRError extends Error { + constructor(message) { + super(message ?? 'Unknown Refresh Token Error'); + + this.name = 'RTRError'; + } +} + +/** + * UnexpectedResourceError + * Thrown when + */ +export class UnexpectedResourceError extends Error { + constructor(resource) { + super('Expected a string, URL, or Request but did not receive one.'); + + this.name = 'UnexpectedResourceError'; + this.resource = resource; + } +} diff --git a/src/components/Root/Events.js b/src/components/Root/Events.js new file mode 100644 index 000000000..bbf2bd122 --- /dev/null +++ b/src/components/Root/Events.js @@ -0,0 +1,5 @@ +/** dispatched during RTR when it is successful */ +export const RTR_SUCCESS_EVENT = '@folio/stripes/core::RTRSuccess'; + +/** dispatched during RTR if RTR itself fails */ +export const RTR_ERROR_EVENT = '@folio/stripes/core::RTRError'; diff --git a/src/components/Root/FFetch.js b/src/components/Root/FFetch.js new file mode 100644 index 000000000..adc69bfdd --- /dev/null +++ b/src/components/Root/FFetch.js @@ -0,0 +1,271 @@ +/* eslint-disable import/prefer-default-export */ + +/** + * TLDR: override global `fetch` and `XMLHttpRequest` to perform RTR for FOLIO API requests. + * + * RTR Primers: + * @see https://authjs.dev/guides/basics/refresh-token-rotation + * @see https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation + * + * Our AT and RT live in HTTP-only cookies, so the JS code doesn't have access + * to them. The AT cookie accompanies every request, and the RT cookie is sent + * only in refresh requests. + * + * The basic workflow here is intercept requests for FOLIO APIs and trap + * response failures that are caused by expired ATs, conduct RTR, then replay + * the original requests. FOLIO API requests that arrive while RTR is in-process + * are held until RTR finishes and then allowed to flow through. AT failure is + * recognized in a response with status code 403 and an error message beginning with + * "Token missing". (Eventually, it may be 401 instead, but not today.) Requests + * to non-FOLIO APIs flow through without intervention. + * + * RTR failures should cause logout since they indicate an expired or + * otherwise invalid RT, which is unrecoverable. Other request failures + * should be handled locally within the applications that initiated the + * requests. + * + * The gross gory details: + * In an ideal world, we would simply export a function and a class and + * tell folks to use those, but we don't live in that world, at least not + * yet. So. For now, we override the global implementations in the constructor + * :scream: so any calls directly invoking `fetch()` or instantiating + * `XMLHttpRequest` get these updated versions that handle token rotation + * automatically. + * + */ + +import { okapi } from 'stripes-config'; +import { getTokenExpiry } from '../../loginServices'; +import { + isFolioApiRequest, + isLogoutRequest, + isValidAT, + isValidRT, + resourceMapper, + rtr, +} from './token-util'; +import { + RTRError, + UnexpectedResourceError, +} from './Errors'; +import { + RTR_ERROR_EVENT, +} from './Events'; + +import FXHR from './FXHR'; + +const OKAPI_FETCH_OPTIONS = { + credentials: 'include', + mode: 'cors', +}; + +export class FFetch { + constructor({ logger }) { + this.logger = logger; + + // save a reference to fetch, and then reassign the global :scream: + this.nativeFetch = global.fetch; + global.fetch = this.ffetch; + + this.NativeXHR = global.XMLHttpRequest; + global.XMLHttpRequest = FXHR(this); + } + + /** { atExpires, rtExpires } both are JS millisecond timestamps */ + tokenExpiration = null; + + /** lock to indicate whether a rotation request is already in progress */ + // @@ needs to be stored in localforage??? + isRotating = false; + + /** + * isPermissibleRequest + * Some requests are always permissible, e.g. auth-n and forgot-password. + * Others are only permissible if the Access Token is still valid. + * + * @param {Request} req clone of the original event.request object + * @param {object} te token expiration shaped like { atExpires, rtExpires } + * @param {string} oUrl Okapi URL + * @returns boolean true if the AT is valid or the request is always permissible + */ + isPermissibleRequest = (resource, te, oUrl) => { + if (isValidAT(te, this.logger)) { + return true; + } + + const isPermissibleResource = (string) => { + const permissible = [ + '/bl-users/forgotten/password', + '/bl-users/forgotten/username', + '/bl-users/login-with-expiry', + '/bl-users/password-reset', + '/saml/check', + ]; + + this.logger.log('rtr', `AT invalid for ${resource}`); + return !!permissible.find(i => string.startsWith(`${oUrl}${i}`)); + }; + + + try { + return resourceMapper(resource, isPermissibleResource); + } catch (rme) { + if (rme instanceof UnexpectedResourceError) { + console.warn(rme.message, resource); // eslint-disable-line no-console + return false; + } + + throw rme; + } + }; + + /** + * passThroughWithRT + * Perform RTR then execute the original request. + * If RTR fails, dispatch RTR_ERROR_EVENT and die softly. + * + * @param {*} resource one of string, URL, Request + * @params {object} options + * @returns Promise + */ + passThroughWithRT = (resource, options) => { + this.logger.log('rtr', 'pre-rtr-fetch', resource); + return rtr(this) + .then(() => { + this.logger.log('rtr', 'post-rtr-fetch', resource); + return this.nativeFetch.apply(global, [resource, options && { ...options, ...OKAPI_FETCH_OPTIONS }]); + }) + .catch(err => { + if (err instanceof RTRError) { + console.error('RTR failure', err); // eslint-disable-line no-console + document.dispatchEvent(new Event(RTR_ERROR_EVENT, { detail: err })); + return Promise.resolve(new Response(JSON.stringify({}))); + } + + throw err; + }); + }; + + /** + * passThroughWithAT + * Given we believe the AT to be valid, pass the fetch through. + * If it fails, maybe our beliefs were wrong, maybe everything is wrong, + * maybe there is no God, or there are many gods, or god is a she, or + * she is a he, or Lou Reed is god. Or maybe we were just wrong about the + * AT and we need to conduct token rotation, so try that. If RTR succeeds, + * it'll pass through the fetch as we originally intended because now we + * know the AT will be valid. If RTR fails, then it doesn't matter about + * Lou Reed. He may be god, but this is out of our hands now. + * + * @param {*} resource any resource acceptable to fetch() + * @param {*} options + * @returns Promise + */ + passThroughWithAT = (resource, options) => { + return this.nativeFetch.apply(global, [resource, options && { ...options, ...OKAPI_FETCH_OPTIONS }]) + .then(response => { + // if the request failed due to a missing token, attempt RTR (which + // will then replay the original fetch if it succeeds), or die softly + // if it fails. return any other response as-is. + if (response.status === 400 && response.headers.get('content-type') === 'text/plain') { + const res = response.clone(); + return res.text() + .then(text => { + if (text.startsWith('Token missing')) { + this.logger.log('rtr', ' (whoops, invalid AT; retrying)'); + return this.passThroughWithRT(resource, options); + } + + // yes, we got a 4xx, but not an RTR 4xx. leave that to the + // original application to handle. it's not our problem. + return response; + }); + } + + return response; + }); + } + + /** + * passThroughLogout + * The logout request should never fail, even if it fails. + * That is, if it fails, we just pretend like it never happened + * instead of blowing up and causing somebody to get stuck in the + * logout process. + * + * @param {*} resource any resource acceptable to fetch() + * @param {object} options + * @returns Promise + */ + passThroughLogout = (resource, options) => { + this.logger.log('rtr', ' (logout request)'); + return this.nativeFetch.apply(global, [resource, options && { ...options, ...OKAPI_FETCH_OPTIONS }]) + .catch(err => { + // kill me softly: return an empty response to allow graceful failure + console.error('-- (rtr-sw) logout failure', err); // eslint-disable-line no-console + return Promise.resolve(new Response(JSON.stringify({}))); + }); + }; + + /** + * passThrough + * Inspect resource to determine whether it's a FOLIO API request. + * Handle it with RTR if it is; let it trickle through if not. + * + * Given we believe the AT to be valid, pass the fetch through. + * If it fails, maybe our beliefs were wrong, maybe everything is wrong, + * maybe there is no God, or there are many gods, or god is a she, or + * she is a he, or Lou Reed is god. Or maybe we were just wrong about the + * AT and we need to conduct token rotation, so try that. If RTR succeeds, + * yay, pass through the fetch as we originally intended because now we + * know the AT will be valid. If RTR fails, then it doesn't matter about + * Lou Reed. He may be god. We'll dispatch an RTR_ERROR_EVENT and then + * return a dummy promise, which gives the root-level (stripes-core level) + * event handler the opportunity to respond (presumably by logging out) + * without tripping up the application-level error handler which isn't + * responsible for handling such things. + * + * @param {*} resource any resource acceptable to fetch() + * @param {object} options + * @returns Promise + * @throws if any fetch fails + */ + ffetch = async (resource, options) => { + // FOLIO API requests are subject to RTR + if (isFolioApiRequest(resource, okapi.url)) { + this.logger.log('rtr', 'will fetch', resource); + + // logout requests must not fail + if (isLogoutRequest(resource, okapi.url)) { + return this.passThroughLogout(resource, options); + } + + // if our cached tokens appear to have expired, pull them from storage. + // maybe another window updated them for us without us knowing. + if (!isValidAT(this.tokenExpiration, this.logger)) { + this.logger.log('rtr', 'local tokens expired; fetching from storage'); + this.tokenExpiration = await getTokenExpiry(); + } + + // AT is valid or unnecessary; execute the fetch + if (this.isPermissibleRequest(resource, this.tokenExpiration, okapi.url)) { + return this.passThroughWithAT(resource, options); + } + + // AT was expired, but RT is valid; perform RTR then execute the fetch + if (isValidRT(this.tokenExpiration, this.logger)) { + return this.passThroughWithRT(resource, options); + } + + // AT is expired. RT is expired. It's the end of the world as we know it. + // So, maybe Michael Stipe is god. Oh, wait, crap, he lost his religion. + // Look, RTR is complicated, what do you want? + console.error('All tokens expired'); // eslint-disable-line no-console + document.dispatchEvent(new Event(RTR_ERROR_EVENT, { detail: 'All tokens expired' })); + return Promise.resolve(new Response(JSON.stringify({}))); + } + + // default: pass requests through to the network + return Promise.resolve(this.nativeFetch.apply(global, [resource, options])); + }; +} diff --git a/src/components/Root/FFetch.test.js b/src/components/Root/FFetch.test.js new file mode 100644 index 000000000..6b8372ccd --- /dev/null +++ b/src/components/Root/FFetch.test.js @@ -0,0 +1,254 @@ +// yeah, eslint, we're doing something kinda dicey here, using +// FFetch for the reassign globals side-effect in its constructor. +/* eslint-disable no-unused-vars */ + +import { getTokenExpiry } from '../../loginServices'; +import { FFetch } from './FFetch'; +import { RTRError, UnexpectedResourceError } from './Errors'; + +jest.mock('../../loginServices', () => ({ + ...(jest.requireActual('../../loginServices')), + setTokenExpiry: jest.fn(() => Promise.resolve()), + getTokenExpiry: jest.fn(() => Promise.resolve()) +})); + +jest.mock('stripes-config', () => ({ + url: 'okapiUrl', + tenant: 'okapiTenant', + okapi: { + url: 'okapiUrl', + tenant: 'okapiTenant' + } +}), +{ virtual: true }); + +const log = jest.fn(); + +const mockFetch = jest.fn(); + +describe('FFetch class', () => { + beforeEach(() => { + global.fetch = mockFetch; + getTokenExpiry.mockResolvedValue({ + atExpires: Date.now() + (10 * 60 * 1000), + rtExpires: Date.now() + (10 * 60 * 1000), + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('Calling a non-okapi fetch', () => { + it('calls native fetch once', async () => { + mockFetch.mockResolvedValueOnce('non-okapi-success'); + const testFfetch = new FFetch({ logger: { log } }); + const response = await global.fetch('nonOkapiURL', { testOption: 'test' }); + await expect(mockFetch.mock.calls).toHaveLength(1); + expect(response).toEqual('non-okapi-success'); + }); + }); + + describe('logging out', () => { + it('calls native fetch once to log out', async () => { + mockFetch.mockResolvedValueOnce('logged out'); + const testFfetch = new FFetch({ logger: { log } }); + const response = await global.fetch('okapiUrl/authn/logout', { testOption: 'test' }); + expect(mockFetch.mock.calls).toHaveLength(1); + expect(response).toEqual('logged out'); + }); + }); + + describe('logging out fails', () => { + it('calls native fetch once to log out', async () => { + mockFetch.mockImplementationOnce(() => new Promise((res, rej) => rej())); + + const testFfetch = new FFetch({ logger: { log } }); + const response = await global.fetch('okapiUrl/authn/logout', { testOption: 'test' }); + expect(mockFetch.mock.calls).toHaveLength(1); + expect(response).toEqual(new Response(JSON.stringify({}))); + }); + }); + + describe('logging out', () => { + it('Calling an okapi fetch with valid token...', async () => { + mockFetch.mockResolvedValueOnce('okapi success'); + const testFfetch = new FFetch({ logger: { log } }); + const response = await global.fetch('okapiUrl/valid', { testOption: 'test' }); + expect(mockFetch.mock.calls).toHaveLength(1); + expect(response).toEqual('okapi success'); + }); + }); + + describe('Calling an okapi fetch with missing token...', () => { + it('triggers rtr...calls fetch 3 times, failed call, token call, successful call', async () => { + mockFetch.mockResolvedValue('success') + .mockResolvedValueOnce(new Response( + 'Token missing', + { + status: 400, + headers: { + 'content-type': 'text/plain', + }, + } + )) + .mockResolvedValueOnce(new Response(JSON.stringify({ + accessTokenExpiration: new Date().getTime() + 1000, + refreshTokenExpiration: new Date().getTime() + 2000, + }), { ok: true })); + const testFfetch = new FFetch({ logger: { log } }); + const response = await global.fetch('okapiUrl', { testOption: 'test' }); + expect(mockFetch.mock.calls).toHaveLength(3); + expect(mockFetch.mock.calls[1][0]).toEqual('okapiUrl/authn/refresh'); + expect(response).toEqual('success'); + }); + }); + + describe('Calling an okapi fetch with expired AT...', () => { + it('triggers rtr...calls fetch 2 times - token call, successful call', async () => { + getTokenExpiry.mockResolvedValueOnce({ + atExpires: Date.now() - (10 * 60 * 1000), + rtExpires: Date.now() + (10 * 60 * 1000), + }); + mockFetch.mockResolvedValue('token rotation success') + .mockResolvedValueOnce(new Response(JSON.stringify({ + accessTokenExpiration: new Date().getTime() + 1000, + refreshTokenExpiration: new Date().getTime() + 2000, + }), { ok: true })); + const testFfetch = new FFetch({ logger: { log } }); + const response = await global.fetch('okapiUrl', { testOption: 'test' }); + expect(mockFetch.mock.calls).toHaveLength(2); + expect(mockFetch.mock.calls[0][0]).toEqual('okapiUrl/authn/refresh'); + expect(response).toEqual('token rotation success'); + }); + }); + + describe('Calling an okapi fetch with valid token, but failing request...', () => { + it('returns response from failed fetch, only calls fetch once.', async () => { + mockFetch.mockResolvedValue('success') + .mockResolvedValueOnce(new Response( + 'An error occurred', + { + status: 403, + headers: { + 'content-type': 'text/plain', + }, + } + )); + const testFfetch = new FFetch({ logger: { log } }); + const response = await global.fetch('okapiUrl', { testOption: 'test' }); + const message = await response.text(); + expect(mockFetch.mock.calls).toHaveLength(1); + expect(message).toEqual('An error occurred'); + }); + }); + + describe('Calling an okapi fetch with missing token and failing rotation...', () => { + it('triggers rtr...calls fetch 2 times, failed call, failed token call, throws error', async () => { + mockFetch.mockResolvedValue('success') + .mockResolvedValueOnce(new Response( + 'Token missing', + { + status: 400, + headers: { + 'content-type': 'text/plain', + }, + } + )) + .mockRejectedValueOnce(new Error('token error message')); + const testFfetch = new FFetch({ logger: { log } }); + try { + await global.fetch('okapiUrl', { testOption: 'test' }); + } catch (e) { + expect(e.toString()).toEqual('Error: token error message'); + expect(mockFetch.mock.calls).toHaveLength(2); + expect(mockFetch.mock.calls[1][0]).toEqual('okapiUrl/authn/refresh'); + } + }); + }); + + describe('Calling an okapi fetch with missing token and reported error from auth service...', () => { + it('throws an RTR error', async () => { + mockFetch.mockResolvedValue('success') + .mockResolvedValueOnce(new Response( + 'Token missing', + { + status: 400, + headers: { + 'content-type': 'text/plain', + }, + } + )) + .mockResolvedValueOnce(new Response( + JSON.stringify({ errors: ['missing token-getting ability'] }), + { + status: 303, + headers: { + 'content-type': 'application/json', + } + } + )); + const testFfetch = new FFetch({ logger: { log } }); + try { + await global.fetch('okapiUrl', { testOption: 'test' }); + } catch (e) { + expect(e instanceof RTRError).toBeTrue; + expect(mockFetch.mock.calls).toHaveLength(2); + expect(mockFetch.mock.calls[1][0]).toEqual('okapiUrl/authn/refresh'); + } + }); + }); + + describe('Calling an okapi fetch when all tokens are expired', () => { + it('triggers an RTR error', async () => { + getTokenExpiry.mockResolvedValueOnce({ + atExpires: Date.now() - (10 * 60 * 1000), + rtExpires: Date.now() - (10 * 60 * 1000), + }); + mockFetch.mockResolvedValue('success') + .mockResolvedValueOnce(new Response( + JSON.stringify({ errors: ['missing token-getting ability'] }), + { + status: 303, + headers: { + 'content-type': 'application/json', + } + } + )); + const testFfetch = new FFetch({ logger: { log } }); + try { + await global.fetch('okapiUrl', { testOption: 'test' }); + } catch (e) { + expect(e instanceof RTRError).toBeTrue; + expect(mockFetch.mock.calls).toHaveLength(2); + expect(mockFetch.mock.calls[1][0]).toEqual('okapiUrl/authn/refresh'); + } + }); + }); + + describe('Calling an okapi fetch with a malformed resource', () => { + it('triggers an Unexpected Resource Error', async () => { + getTokenExpiry.mockResolvedValueOnce({ + atExpires: Date.now() - (10 * 60 * 1000), + rtExpires: Date.now() - (10 * 60 * 1000), + }); + mockFetch.mockResolvedValue('success') + .mockResolvedValueOnce(new Response( + JSON.stringify({ errors: ['missing token-getting ability'] }), + { + status: 303, + headers: { + 'content-type': 'application/json', + } + } + )); + const testFfetch = new FFetch({ logger: { log } }); + try { + await global.fetch({ foo: 'okapiUrl' }, { testOption: 'test' }); + } catch (e) { + expect(e instanceof UnexpectedResourceError).toBeTrue; + expect(mockFetch.mock.calls).toHaveLength(0); + } + }); + }); +}); diff --git a/src/components/Root/FXHR.js b/src/components/Root/FXHR.js new file mode 100644 index 000000000..c607e2e92 --- /dev/null +++ b/src/components/Root/FXHR.js @@ -0,0 +1,30 @@ +import { okapi } from 'stripes-config'; +import { isFolioApiRequest, rtr } from './token-util'; + +export default (deps) => { + return class FXHRClass extends XMLHttpRequest { + constructor() { + super(); + this.shouldEnsureToken = false; + this.FFetchContext = deps; + } + + open = (method, url) => { + this.FFetchContext.logger?.log('rtr', 'capture XHR.open'); + this.shouldEnsureToken = isFolioApiRequest(url, okapi.url); + super.open(method, url); + } + + send = async (payload) => { + this.FFetchContext.logger?.log('rtr', 'capture XHR send'); + if (this.shouldEnsureToken) { + await rtr(this.FFetchContext) + .then(() => { + super.send(payload); + }); + } else { + super.send(payload); + } + } + }; +}; diff --git a/src/components/Root/FXHR.test.js b/src/components/Root/FXHR.test.js new file mode 100644 index 000000000..6bee94523 --- /dev/null +++ b/src/components/Root/FXHR.test.js @@ -0,0 +1,50 @@ +import { rtr } from './token-util'; +import FXHR from './FXHR'; + +jest.mock('./token-util', () => ({ + ...(jest.requireActual('./token-util')), + rtr: jest.fn(() => new Promise()), +})); + +const openSpy = jest.spyOn(XMLHttpRequest.prototype, 'open').mockImplementation(); +const sendSpy = jest.spyOn(XMLHttpRequest.prototype, 'send').mockImplementation(() => {}); +const aelSpy = jest.spyOn(XMLHttpRequest.prototype, 'addEventListener').mockImplementation(); + +const mockHandler = jest.fn(() => {}); + +describe('FXHR', () => { + let FakeXHR; + let testXHR; + beforeEach(() => { + jest.clearAllMocks(); + FakeXHR = FXHR({ logger: { log: () => {} } }); + testXHR = new FakeXHR(); + }); + + it('instantiates without error', () => { + expect(FakeXHR).toBeDefined; + expect(testXHR).toBeDefined; + }); + + it('calls inherited open method', () => { + testXHR.open('POST', 'okapiUrl'); + expect(openSpy.mock.calls).toHaveLength(1); + }); + + it('calls inherited send method', () => { + rtr.mockResolvedValue(); + testXHR.open('POST', 'notOkapi'); + testXHR.send(new ArrayBuffer(8)); + expect(openSpy.mock.calls).toHaveLength(1); + expect(sendSpy.mock.calls).toHaveLength(1); + }); + + it('calls other prototype methods...', () => { + rtr.mockResolvedValue(); + testXHR.addEventListener('abort', mockHandler); + testXHR.open('POST', 'okapiUrl'); + testXHR.send(new ArrayBuffer(8)); + expect(openSpy.mock.calls).toHaveLength(1); + expect(aelSpy.mock.calls).toHaveLength(1); + }); +}); diff --git a/src/components/Root/Root.js b/src/components/Root/Root.js index b4b549cc6..ecba25a32 100644 --- a/src/components/Root/Root.js +++ b/src/components/Root/Root.js @@ -20,8 +20,8 @@ import initialReducers from '../../initialReducers'; import enhanceReducer from '../../enhanceReducer'; import createApolloClient from '../../createApolloClient'; import createReactQueryClient from '../../createReactQueryClient'; -import { setSinglePlugin, setBindings, setOkapiToken, setTimezone, setCurrency, updateCurrentUser } from '../../okapiActions'; -import { loadTranslations, checkOkapiSession } from '../../loginServices'; +import { setSinglePlugin, setBindings, setIsAuthenticated, setOkapiToken, setTimezone, setCurrency, updateCurrentUser } from '../../okapiActions'; +import { loadTranslations, checkOkapiSession, addRtrEventListeners } from '../../loginServices'; import { getQueryResourceKey, getCurrentModule } from '../../locationService'; import Stripes from '../../Stripes'; import RootWithIntl from '../../RootWithIntl'; @@ -30,6 +30,7 @@ import SystemSkeleton from '../SystemSkeleton'; import './Root.css'; import { withModules } from '../Modules'; +import { FFetch } from './FFetch'; if (!metadata) { // eslint-disable-next-line no-console @@ -40,7 +41,7 @@ class Root extends Component { constructor(...args) { super(...args); - const { modules, history, okapi } = this.props; + const { modules, history, okapi, store } = this.props; this.reducers = { ...initialReducers }; this.epics = {}; @@ -64,6 +65,14 @@ class Root extends Component { this.apolloClient = createApolloClient(okapi); this.reactQueryClient = createReactQueryClient(); + + // enhanced security mode: + // * configure fetch and xhr interceptors to conduct RTR + // * configure document-level event listeners to listen for RTR events + if (this.props.config.useSecureTokens) { + this.ffetch = new FFetch({ logger: this.props.logger }); + addRtrEventListeners(okapi, store); + } } getChildContext() { @@ -107,7 +116,7 @@ class Root extends Component { } render() { - const { logger, store, epics, config, okapi, actionNames, token, disableAuth, currentUser, currentPerms, locale, defaultTranslations, timezone, currency, plugins, bindings, discovery, translations, history, serverDown } = this.props; + const { logger, store, epics, config, okapi, actionNames, token, isAuthenticated, disableAuth, currentUser, currentPerms, locale, defaultTranslations, timezone, currency, plugins, bindings, discovery, translations, history, serverDown } = this.props; if (serverDown) { return
Error: server is down.
; @@ -126,6 +135,7 @@ class Root extends Component { okapi, withOkapi: this.withOkapi, setToken: (val) => { store.dispatch(setOkapiToken(val)); }, + setIsAuthenticated: (val) => { store.dispatch(setIsAuthenticated(val)); }, actionNames, locale, timezone, @@ -167,6 +177,7 @@ class Root extends Component { @@ -192,6 +203,7 @@ Root.propTypes = { replaceReducer: PropTypes.func.isRequired, }), token: PropTypes.string, + isAuthenticated: PropTypes.bool, disableAuth: PropTypes.bool.isRequired, logger: PropTypes.object.isRequired, currentPerms: PropTypes.object, @@ -249,6 +261,7 @@ function mapStateToProps(state) { currentPerms: state.okapi.currentPerms, currentUser: state.okapi.currentUser, discovery: state.discovery, + isAuthenticated: state.okapi.isAuthenticated, locale: state.okapi.locale, okapi: state.okapi, okapiReady: state.okapi.okapiReady, diff --git a/src/components/Root/token-util.js b/src/components/Root/token-util.js new file mode 100644 index 000000000..2ebd9c7f4 --- /dev/null +++ b/src/components/Root/token-util.js @@ -0,0 +1,314 @@ +import { okapi } from 'stripes-config'; + +import { setTokenExpiry } from '../../loginServices'; +import { RTRError, UnexpectedResourceError } from './Errors'; +import { RTR_SUCCESS_EVENT } from './Events'; + +/** + * RTR_TTL_WINDOW (float) + * How much of a token's TTL can elapse before it is considered expired? + * This helps us avoid a race-like condition where a token expires in the + * gap between when we check whether we think it's expired and when we use + * it to authorize a new request. Say the last RTR response took a long time + * to arrive, so it was generated at 12:34:56 but we didn't process it until + * 12:34:59. That could cause problems if (just totally hypothetically) we + * had an application (again, TOTALLY hypothetically) that was polling every + * five seconds and one of its requests landed in that three-second gap. Oh, + * hey STCOR-754, what are you doing here? + * + * So this is a buffer. Instead of letting a token be used up until the very + * last second of its life, we'll consider it expired a little early. This will + * cause RTR to happen a little early (i.e. a little more frequently) but that + * should be OK since it increases our confidence that when an AT accompanies + * the RTR request it is still valid. + * + * 0 < value < 1. Closer to 0 means more frequent rotation. Closer to 1 means + * closer to the exact value of its TTL. 0.8 is just a SWAG at a "likely to be + * useful" value. Given a 600 second TTL (the current default for ATs) it + * corresponds to 480 seconds. + */ +export const RTR_TTL_WINDOW = 0.8; + +/** localstorage flag indicating whether an RTR request is already under way. */ +export const RTR_IS_ROTATING = '@folio/stripes/core::rtrIsRotating'; + +/** + * RTR_MAX_AGE (int) + * How long do we let a refresh request last before we consider it stale? + * + * When RTR begins, the current time in milliseconds (i.e. Date.now()) is + * cached in localStorage and the existence of that value is used as a flag + * in subsequent requests to indicate that they just need to wait for the + * existing RTR request to complete rather than starting another RTR request + * (which would fail due to reusing the same RT). The flag is cleared in a + * `finally` clause, so it should _always_ get cleared, but closing a window + * mid-rotation allows the flag to get stuck. If the flag is present but no + * rotation request is actually in progress, rotation will never happen. + * + * Thus this value, a time (in milliseconds) representing the max-age of an + * rtr request before it is considered stale/ignorable. + * + * Time in milliseconds + */ +export const RTR_MAX_AGE = 2000; + +/** + * resourceMapper + * Given a resource that represent a URL in some form (a string + * such as "https://barbenheimer-for-best-picture.com" or a URL object + * or a Request object), and a function that expects a string, extract + * the URL as a string and pass it to the function. Throw if the + * resource doesn't represent a known type. + * + * @param {string|URL|Request} resource + * @param {*} fx function to call + * @returns boolean + * @throws UnexpectedResourceError if resource is not a string, URL, or Request + */ +export const resourceMapper = (resource, fx) => { + if (typeof resource === 'string') { + return fx(resource); + } else if (resource instanceof URL) { + return fx(resource.origin); + } else if (resource instanceof Request) { + return fx(resource.url); + } + + throw new UnexpectedResourceError(resource); +}; + +/** + * isLogoutRequest + * Return true if the given resource is a logout request; false otherwise. + * + * @param {*} resource one of string, URL, Request + * @param {string} oUrl FOLIO API origin + * @returns boolean + */ +export const isLogoutRequest = (resource, oUrl) => { + const permissible = [ + '/authn/logout', + ]; + + const isLogoutResource = (string) => { + return !!permissible.find(i => string.startsWith(`${oUrl}${i}`)); + }; + + try { + return resourceMapper(resource, isLogoutResource); + } catch (rme) { + if (rme instanceof UnexpectedResourceError) { + console.warn(rme.message, resource); // eslint-disable-line no-console + return false; + } + + throw rme; + } +}; + +/** + * isFolioApiRequest + * Return true if the resource origin matches FOLIO's API origin, i.e. if + * this is a request that needs to include a valid AT. + * + * @param {*} resource one of string, URL, request + * @param {string} oUrl FOLIO API origin + * @returns boolean + */ +export const isFolioApiRequest = (resource, oUrl) => { + const isFolioApiResource = (string) => { + return string.startsWith(oUrl); + }; + + try { + return resourceMapper(resource, isFolioApiResource); + } catch (rme) { + if (rme instanceof UnexpectedResourceError) { + console.warn(rme.message, resource); // eslint-disable-line no-console + return false; + } + + throw rme; + } +}; + +/** + * isValidAT + * Return true if tokenExpiration.atExpires is in the future; false otherwise. + * + * @param {object} te tokenExpiration shaped like { atExpires, rtExpires } + * @param {@folio/stripes/logger} logger + * @returns boolean + */ +export const isValidAT = (te, logger) => { + const isValid = !!(te?.atExpires > Date.now()); + logger.log('rtr', `AT isValid? ${isValid}; expires ${new Date(te?.atExpires || null).toISOString()}`); + return isValid; +}; + +/** + * isValidRT + * Return true if tokenExpiration.rtExpires is in the future; false otherwise. + * + * @param {object} te tokenExpiration shaped like { atExpires, rtExpires } + * @param {@folio/stripes/logger} logger + * @returns boolean + */ +export const isValidRT = (te, logger) => { + const isValid = !!(te?.rtExpires > Date.now()); + logger.log('rtr', `RT isValid? ${isValid}; expires ${new Date(te?.rtExpires || null).toISOString()}`); + return isValid; +}; + +/** + * adjustTokenExpiration + * Set the AT and RT token expirations to the fraction of their TTL given by + * RTR_TTL_WINDOW. e.g. if a token should be valid for 100 more seconds and + * RTR_TTL_WINDOW is 0.8, set to the expiration time to 80 seconds from now. + * + * @param {object} value { tokenExpiration: { atExpires, rtExpires }} both are millisecond timestamps + * @param {number} fraction float in the range (0..1] + * @returns { tokenExpiration: { atExpires, rtExpires }} both are millisecond timestamps + */ +export const adjustTokenExpiration = (value, fraction) => ({ + atExpires: Date.now() + ((value.tokenExpiration.atExpires - Date.now()) * fraction), + rtExpires: Date.now() + ((value.tokenExpiration.rtExpires - Date.now()) * fraction), +}); + +/** + * shouldRotate + * Return true if we should start a new rotation request, false if a request is + * already pending. + * + * When RTR begins, the current time in milliseconds (i.e. Date.now()) is + * cached in localStorage and the existence of that value is used as a flag + * in subsequent requests to indicate that they should wait for that request + * rather then firing a new one. If that flag isn't properly cleared when the + * RTR request completes, it will block future RTR requests since it will + * appear that a request is already in-progress. Thus, instead of merely + * checking for the presence of the flag, this function ALSO checks the age + * of that flag. If the flag is older than RTR_MAX_AGE it is considered + * stale, indicating a new request should begin. + * + * @param {@folio/stripes/logger} logger + * @returns boolean + */ +export const shouldRotate = (logger) => { + const rotationTimestamp = localStorage.getItem(RTR_IS_ROTATING); + if (rotationTimestamp) { + if (Date.now() - rotationTimestamp < RTR_MAX_AGE) { + return false; + } + logger.log('rtr', 'rotation request is stale'); + } + + return true; +}; + +/** + * rtr + * exchange an RT for a new one. + * Make a POST request to /authn/refresh, including the current credentials, + * and send a TOKEN_EXPIRATION event to clients that includes the new AT/RT + * expiration timestamps. + * + * Since all windows share the same cookie, this means the must also share + * rotation, and when rotation starts in one window requests in all others + * must await the same promise. Thus, the isRotating flag is stored in + * localstorage (rather than a local variable) where it is globally accessible, + * rather than in a local variable (which would only be available in the scope + * of a single window). + * + * The basic plot is for this function to return a promise that resolves when + * rotation is finished. If rotation hasn't started, that's the rotation + * promise itself (with some other business chained on). If rotation has + * started, it's a promise that resolves when it receives a "rotation complete" + * event (part of that other business). + * + * The other business consists of: + * 1 unsetting the isRotating flag in localstorage + * 2 capturing the new expiration data, shrinking its TTL window, calling + * setTokenExpiry to push the new values to localstorage, and caching it + * on the calling context. + * 3 dispatch RTR_SUCCESS_EVENT + * + * @returns Promise + * @throws if RTR fails + */ +export const rtr = async (context) => { + context.logger.log('rtr', '** RTR ...'); + + let rtrPromise = null; + if (shouldRotate(context.logger)) { + localStorage.setItem(RTR_IS_ROTATING, `${Date.now()}`); + rtrPromise = context.nativeFetch.apply(global, [`${okapi.url}/authn/refresh`, { + headers: { + 'content-type': 'application/json', + 'x-okapi-tenant': okapi.tenant, + }, + method: 'POST', + credentials: 'include', + mode: 'cors', + }]) + .then(res => { + if (res.ok) { + return res.json(); + } + // rtr failure. return an error message if we got one. + return res.json() + .then(json => { + localStorage.removeItem(RTR_IS_ROTATING); + if (Array.isArray(json.errors) && json.errors[0]) { + throw new RTRError(`${json.errors[0].message} (${json.errors[0].code})`); + } else { + throw new RTRError('RTR response failure'); + } + }); + }) + .then(json => { + context.logger.log('rtr', '** success!'); + const te = adjustTokenExpiration({ + tokenExpiration: { + atExpires: new Date(json.accessTokenExpiration).getTime(), + rtExpires: new Date(json.refreshTokenExpiration).getTime(), + } + }, RTR_TTL_WINDOW); + context.tokenExpiration = te; + return setTokenExpiry(te); + }) + .finally(() => { + localStorage.removeItem(RTR_IS_ROTATING); + window.dispatchEvent(new Event(RTR_SUCCESS_EVENT)); + }); + } else { + // isRotating is true, so rotation has already started. + // create a new promise that resolves when it receives + // either an RTR_SUCCESS_EVENT or storage event and + // the isRotating value in storage is false, indicating rotation + // has completed. + // + // the promise itself sets up the listener, and cancels it when + // it resolves. + context.logger.log('rtr', 'rotation is already pending!'); + rtrPromise = new Promise((res) => { + const rotationHandler = () => { + if (localStorage.getItem(RTR_IS_ROTATING) === null) { + window.removeEventListener(RTR_SUCCESS_EVENT, rotationHandler); + window.removeEventListener('storage', rotationHandler); + context.logger.log('rtr', 'token rotation has resolved, continue as usual!'); + res(); + } + }; + // same window: listen for custom event + window.addEventListener(RTR_SUCCESS_EVENT, rotationHandler); + + // other windows: listen for storage event + // @see https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event + // "This [is] a way for other pages on the domain using the storage + // to sync any changes that are made." + window.addEventListener('storage', rotationHandler); + }); + } + + return rtrPromise; +}; diff --git a/src/components/Root/token-util.test.js b/src/components/Root/token-util.test.js new file mode 100644 index 000000000..42f3ae2ac --- /dev/null +++ b/src/components/Root/token-util.test.js @@ -0,0 +1,312 @@ +import { RTRError, UnexpectedResourceError } from './Errors'; +import { + isFolioApiRequest, + isLogoutRequest, + isValidAT, + isValidRT, + resourceMapper, + rtr, + shouldRotate, + RTR_IS_ROTATING, + RTR_MAX_AGE, +} from './token-util'; +import { RTR_SUCCESS_EVENT } from './Events'; + +describe('isFolioApiRequest', () => { + it('accepts requests whose origin matches okapi\'s', () => { + const oUrl = 'https://millicent-sounds-kinda-like-malificent.edu'; + const req = `${oUrl}/that/is/awkward`; + expect(isFolioApiRequest(req, oUrl)).toBe(true); + }); + + it('rejects requests whose origin does not match okapi\'s', () => { + const req = 'https://skipper-seriously-skipper.org'; + expect(isFolioApiRequest(req, 'https://anything-but-skipper.edu')).toBe(false); + }); + + it('rejects invalid resource input', () => { + const req = { 'ken': 'not kenough' }; + expect(isFolioApiRequest(req, 'https://sorry-dude.edu')).toBe(false); + }); +}); + +describe('isLogoutRequest', () => { + it('accepts logout endpoints', () => { + const path = '/authn/logout'; + + expect(isLogoutRequest(path, '')).toBe(true); + }); + + it('rejects unknown endpoints', () => { + const path = '/maybe/oppie/would/have/been/happier/in/malibu'; + + expect(isLogoutRequest(path, '')).toBe(false); + }); + + it('rejects invalid input', () => { + const path = { wat: '/maybe/oppie/would/have/been/happier/in/malibu' }; + expect(isLogoutRequest(path, '')).toBe(false); + }); +}); + +describe('isValidAT', () => { + it('returns true for valid ATs', () => { + const logger = { log: jest.fn() }; + expect(isValidAT({ atExpires: Date.now() + 1000 }, logger)).toBe(true); + expect(logger.log).toHaveBeenCalled(); + }); + + it('returns false for expired ATs', () => { + const logger = { log: jest.fn() }; + expect(isValidAT({ atExpires: Date.now() - 1000 }, logger)).toBe(false); + expect(logger.log).toHaveBeenCalled(); + }); + + it('returns false when AT info is missing', () => { + const logger = { log: jest.fn() }; + expect(isValidAT({ monkey: 'bagel' }, logger)).toBe(false); + expect(logger.log).toHaveBeenCalled(); + }); +}); + +describe('isValidRT', () => { + it('returns true for valid RTs', () => { + const logger = { log: jest.fn() }; + expect(isValidRT({ rtExpires: Date.now() + 1000 }, logger)).toBe(true); + expect(logger.log).toHaveBeenCalled(); + }); + + it('returns false for expired RTs', () => { + const logger = { log: jest.fn() }; + expect(isValidRT({ rtExpires: Date.now() - 1000 }, logger)).toBe(false); + expect(logger.log).toHaveBeenCalled(); + }); + + it('returns false when RT info is missing', () => { + const logger = { log: jest.fn() }; + expect(isValidRT({ monkey: 'bagel' }, logger)).toBe(false); + expect(logger.log).toHaveBeenCalled(); + }); +}); + +describe('resourceMapper', () => { + const fx = (input) => (input); + + it('accepts strings', () => { + const av = 'barbie'; + expect(resourceMapper(av, fx)).toBe(av); + }); + + it('accepts URLs', () => { + const av = 'https://oppie.com'; + expect(resourceMapper(new URL(av), fx)).toBe(av); + }); + + it('accepts Requests', () => { + const av = 'https://los-alamos-dreamtopia-castle-was-actually-a-nightmare.com/'; + expect(resourceMapper(new Request(av), fx)).toBe(av); + }); + + it('rejects other argument types', () => { + const av = { ken: 'kenough' }; + try { + resourceMapper(av, fx); + } catch (e) { + expect(e instanceof UnexpectedResourceError).toBe(true); + } + }); +}); + +describe('rtr', () => { + it('rotates', async () => { + const context = { + logger: { + log: jest.fn(), + }, + nativeFetch: { + apply: () => Promise.resolve({ + ok: true, + json: () => Promise.resolve({ + accessTokenExpiration: '2023-11-17T10:39:15.000Z', + refreshTokenExpiration: '2023-11-27T10:39:15.000Z' + }), + }) + } + }; + + let res = null; + let ex = null; + try { + res = await rtr(context); + } catch (e) { + ex = e; + } + + expect(res.tokenExpiration).toBeTruthy(); + expect(ex).toBe(null); + }); + + describe('handles simultaneous rotation', () => { + beforeEach(() => { + localStorage.setItem(RTR_IS_ROTATING, Date.now()); + }); + afterEach(() => { + localStorage.removeItem(RTR_IS_ROTATING); + }); + + it('same window (RTR_SUCCESS_EVENT)', async () => { + const context = { + logger: { + log: jest.fn(), + }, + nativeFetch: { + apply: () => Promise.resolve({ + ok: true, + json: () => Promise.resolve({ + accessTokenExpiration: '2023-11-17T10:39:15.000Z', + refreshTokenExpiration: '2023-11-27T10:39:15.000Z' + }), + }) + } + }; + + setTimeout(() => { + window.dispatchEvent(new Event(RTR_SUCCESS_EVENT)); + }, 500); + + setTimeout(() => { + localStorage.removeItem(RTR_IS_ROTATING); + window.dispatchEvent(new Event(RTR_SUCCESS_EVENT)); + }, 1000); + + let ex = null; + try { + await rtr(context); + } catch (e) { + ex = e; + } + + expect(ex).toBe(null); + // expect(window.removeEventListener).toHaveBeenCalled(); + }); + + it('multiple window (storage event)', async () => { + const context = { + logger: { + log: jest.fn(), + }, + nativeFetch: { + apply: () => Promise.resolve({ + ok: true, + json: () => Promise.resolve({ + accessTokenExpiration: '2023-11-17T10:39:15.000Z', + refreshTokenExpiration: '2023-11-27T10:39:15.000Z' + }), + }) + } + }; + + setTimeout(() => { + window.dispatchEvent(new Event('storage')); + }, 500); + + setTimeout(() => { + localStorage.removeItem(RTR_IS_ROTATING); + window.dispatchEvent(new Event('storage')); + }, 1000); + + let ex = null; + try { + await rtr(context); + } catch (e) { + ex = e; + } + + expect(ex).toBe(null); + // expect(window.removeEventListener).toHaveBeenCalledWith('monkey') + }); + }); + + + + it('on known error, throws error', async () => { + const errors = [{ message: 'Actually I love my Birkenstocks', code: 'Chacos are nice, too. Also Tevas' }]; + const context = { + logger: { + log: jest.fn(), + }, + nativeFetch: { + apply: () => Promise.resolve({ + ok: false, + json: () => Promise.resolve({ + errors, + }), + }) + } + }; + + let ex = null; + try { + await rtr(context); + } catch (e) { + ex = e; + } + + expect(ex instanceof RTRError).toBe(true); + expect(ex.message).toMatch(errors[0].message); + expect(ex.message).toMatch(errors[0].code); + }); + + + it('on unknown error, throws generic error', async () => { + const error = 'I love my Birkenstocks. Chacos are nice, too. Also Tevas'; + const context = { + logger: { + log: jest.fn(), + }, + nativeFetch: { + apply: () => Promise.resolve({ + ok: false, + json: () => Promise.resolve({ + error, + }), + }) + } + }; + + let ex = null; + try { + await rtr(context); + } catch (e) { + ex = e; + } + + expect(ex instanceof RTRError).toBe(true); + expect(ex.message).toMatch('RTR response failure'); + }); +}); + +describe('shouldRotate', () => { + afterEach(() => { + localStorage.removeItem(RTR_IS_ROTATING); + }); + + const logger = { + log: jest.fn(), + }; + + it('returns true if key is absent', () => { + localStorage.removeItem(RTR_IS_ROTATING); + expect(shouldRotate(logger)).toBe(true); + }); + + it('returns true if key is expired', () => { + localStorage.setItem(RTR_IS_ROTATING, Date.now() - (RTR_MAX_AGE + 1000)); + expect(shouldRotate(logger)).toBe(true); + }); + + it('returns false if key is active', () => { + localStorage.setItem(RTR_IS_ROTATING, Date.now() - 1); + expect(shouldRotate(logger)).toBe(false); + }); +}); diff --git a/src/createApolloClient.js b/src/createApolloClient.js index 9819bda6c..3894a1c1e 100644 --- a/src/createApolloClient.js +++ b/src/createApolloClient.js @@ -1,12 +1,13 @@ import { InMemoryCache, ApolloClient } from '@apollo/client'; -const createClient = ({ url, tenant, token }) => (new ApolloClient({ - uri: `${url}/graphql`, +const createClient = ({ tenant, token, url }) => (new ApolloClient({ + cache: new InMemoryCache(), + credentials: 'include', headers: { 'X-Okapi-Tenant': tenant, - 'X-Okapi-Token': token, + ...(token && { 'X-Okapi-Token': token }), }, - cache: new InMemoryCache(), + uri: `${url}/graphql`, })); export default createClient; diff --git a/src/discoverServices.js b/src/discoverServices.js index 7c5e33812..f88c4eed2 100644 --- a/src/discoverServices.js +++ b/src/discoverServices.js @@ -3,7 +3,7 @@ import { some } from 'lodash'; function getHeaders(tenant, token) { return { 'X-Okapi-Tenant': tenant, - 'X-Okapi-Token': token, + ...(token && { 'X-Okapi-Token': token }), 'Content-Type': 'application/json' }; } @@ -12,7 +12,9 @@ function fetchOkapiVersion(store) { const okapi = store.getState().okapi; return fetch(`${okapi.url}/_/version`, { - headers: getHeaders(okapi.tenant, okapi.token) + headers: getHeaders(okapi.tenant, okapi.token), + credentials: 'include', + mode: 'cors', }).then((response) => { // eslint-disable-line consistent-return if (response.status >= 400) { store.dispatch({ type: 'DISCOVERY_FAILURE', code: response.status }); @@ -31,7 +33,9 @@ function fetchModules(store) { const okapi = store.getState().okapi; return fetch(`${okapi.url}/_/proxy/tenants/${okapi.tenant}/modules?full=true`, { - headers: getHeaders(okapi.tenant, okapi.token) + headers: getHeaders(okapi.tenant, okapi.token), + credentials: 'include', + mode: 'cors', }).then((response) => { // eslint-disable-line consistent-return if (response.status >= 400) { store.dispatch({ type: 'DISCOVERY_FAILURE', code: response.status }); diff --git a/src/loginServices.js b/src/loginServices.js index d2acceae8..b4d8a2c89 100644 --- a/src/loginServices.js +++ b/src/loginServices.js @@ -1,12 +1,14 @@ import localforage from 'localforage'; -import { translations } from 'stripes-config'; +import { config, translations } from 'stripes-config'; import rtlDetect from 'rtl-detect'; import moment from 'moment'; import { discoverServices } from './discoverServices'; +import { resetStore } from './mainActions'; import { clearCurrentUser, + clearOkapiToken, setCurrentPerms, setLocale, setTimezone, @@ -14,7 +16,7 @@ import { setPlugins, setBindings, setTranslations, - clearOkapiToken, + setIsAuthenticated, setAuthError, checkSSO, setOkapiReady, @@ -24,6 +26,9 @@ import { updateCurrentUser, } from './okapiActions'; import processBadResponse from './processBadResponse'; +import configureLogger from './configureLogger'; + +import { RTR_ERROR_EVENT } from './components/Root/Events'; // export supported locales, i.e. the languages we provide translations for export const supportedLocales = [ @@ -63,17 +68,60 @@ export const supportedNumberingSystems = [ 'arab', // Arabic-Hindi (٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩) ]; +/** name for the session key in local storage */ +const SESSION_NAME = 'okapiSess'; + +/** + * getTokenSess + * simple wrapper around access to values stored in localforage + * to insulate RTR functions from that API. + * + * @returns {object} + */ +export const getOkapiSession = async () => { + return localforage.getItem(SESSION_NAME); +}; + +/** + * getTokenSess + * simple wrapper around access to values stored in localforage + * to insulate RTR functions from that API. + * + * @returns {object} shaped like { atExpires, rtExpires }; each is a millisecond timestamp + */ +export const getTokenExpiry = async () => { + const sess = await getOkapiSession(); + return new Promise((resolve) => resolve(sess?.tokenExpiration)); +}; + +/** + * getTokenSess + * simple wrapper around access to values stored in localforage + * to insulate RTR functions from that API. Supplement the existing + * session with updated token expiration data. + * + * @param {object} shaped like { atExpires, rtExpires }; each is a millisecond timestamp + * @returns {object} updated session object + */ +export const setTokenExpiry = async (te) => { + const sess = await getOkapiSession(); + return localforage.setItem(SESSION_NAME, { ...sess, tokenExpiration: te }); +}; + + // export config values for storing user locale export const userLocaleConfig = { 'configName': 'localeSettings', 'module': '@folio/stripes-core', }; +const logger = configureLogger(config); + function getHeaders(tenant, token) { return { 'X-Okapi-Tenant': tenant, - 'X-Okapi-Token': token, 'Content-Type': 'application/json', + ...(token && { 'X-Okapi-Token': token }), }; } @@ -164,12 +212,15 @@ export function loadTranslations(store, locale, defaultTranslations = {}) { * @returns {Promise} */ function dispatchLocale(url, store, tenant) { - return fetch(url, - { headers: getHeaders(tenant, store.getState().okapi.token) }) + return fetch(url, { + headers: getHeaders(tenant, store.getState().okapi.token), + credentials: 'include', + mode: 'cors', + }) .then((response) => { if (response.status === 200) { response.json().then((json) => { - if (json.configs.length) { + if (json.configs?.length) { const localeValues = JSON.parse(json.configs[0].value); const { locale, timezone, currency } = localeValues; if (locale) { @@ -240,12 +291,15 @@ export function getUserLocale(okapiUrl, store, tenant, userId) { * @returns {Promise} */ export function getPlugins(okapiUrl, store, tenant) { - return fetch(`${okapiUrl}/configurations/entries?query=(module==PLUGINS)`, - { headers: getHeaders(tenant, store.getState().okapi.token) }) + return fetch(`${okapiUrl}/configurations/entries?query=(module==PLUGINS)`, { + headers: getHeaders(tenant, store.getState().okapi.token), + credentials: 'include', + mode: 'cors', + }) .then((response) => { if (response.status < 400) { response.json().then((json) => { - const configs = json.configs.reduce((acc, val) => ({ + const configs = json.configs?.reduce((acc, val) => ({ ...acc, [val.configName]: val.value, }), {}); @@ -266,8 +320,11 @@ export function getPlugins(okapiUrl, store, tenant) { * @returns {Promise} */ export function getBindings(okapiUrl, store, tenant) { - return fetch(`${okapiUrl}/configurations/entries?query=(module==ORG and configName==bindings)`, - { headers: getHeaders(tenant, store.getState().okapi.token) }) + return fetch(`${okapiUrl}/configurations/entries?query=(module==ORG and configName==bindings)`, { + headers: getHeaders(tenant, store.getState().okapi.token), + credentials: 'include', + mode: 'cors', + }) .then((response) => { let bindings = {}; if (response.status >= 400) { @@ -275,7 +332,7 @@ export function getBindings(okapiUrl, store, tenant) { } else { response.json().then((json) => { const configs = json.configs; - if (configs.length > 0) { + if (Array.isArray(configs) && configs.length > 0) { const string = configs[0].value; try { const tmp = JSON.parse(string); @@ -335,33 +392,57 @@ function loadResources(okapiUrl, store, tenant, userId) { */ export function spreadUserWithPerms(userWithPerms) { const user = { - id: userWithPerms.user.id, - username: userWithPerms.user.username, - ...userWithPerms.user.personal, + id: userWithPerms?.user?.id, + username: userWithPerms?.user?.username, + ...userWithPerms?.user?.personal, }; // remap data's array of permission-names to set with // permission-names for keys and `true` for values - const perms = Object.assign({}, ...userWithPerms.permissions.permissions.map(p => ({ [p.permissionName]: true }))); + const perms = Object.assign({}, ...userWithPerms?.permissions?.permissions.map(p => ({ [p.permissionName]: true }))); return { user, perms }; } +/** + * logout + * dispatch events to clear the store, then clear the session too. + * + * @param {object} redux store + * + * @returns {Promise} + */ +export async function logout(okapiUrl, store) { + store.dispatch(setIsAuthenticated(false)); + store.dispatch(clearCurrentUser()); + store.dispatch(clearOkapiToken()); + store.dispatch(resetStore()); + return fetch(`${okapiUrl}/authn/logout`, { + method: 'POST', + mode: 'cors', + credentials: 'include' + }) + .then(localforage.removeItem(SESSION_NAME)) + .then(localforage.removeItem('loginResponse')); +} + /** * createOkapiSession * Remap the given data into a session object shaped like: * { * user: { id, username, personal } + * tenant: string, * perms: { permNameA: true, permNameB: true, ... } - * token: token + * isAuthenticated: boolean, + * tokenExpiration: { atExpires, rtExpires } * } * Dispatch the session object, then return a Promise that fetches * and dispatches tenant resources. * - * @param {*} okapiUrl - * @param {*} store - * @param {*} tenant - * @param {*} token + * @param {string} okapiUrl + * @param {object} store + * @param {string} tenant + * @param {string} token * @param {*} data * * @returns {Promise} @@ -378,54 +459,68 @@ export function createOkapiSession(okapiUrl, store, tenant, token, data) { store.dispatch(setCurrentPerms(perms)); + // if we can't parse tokenExpiration data, e.g. because data comes from `/bl-users/_self` + // which doesn't provide it, then set an invalid AT value and a near-future (+10 minutes) RT value. + // the invalid AT will prompt an RTR cycle which will either give us new AT/RT values + // (if the RT was valid) or throw an RTR_ERROR (if the RT was not valid). + const tokenExpiration = { + atExpires: data.tokenExpiration?.accessTokenExpiration ? new Date(data.tokenExpiration.accessTokenExpiration).getTime() : -1, + rtExpires: data.tokenExpiration?.refreshTokenExpiration ? new Date(data.tokenExpiration.refreshTokenExpiration).getTime() : Date.now() + (10 * 60 * 1000), + }; + const sessionTenant = data.tenant || tenant; const okapiSess = { token, + isAuthenticated: true, user, perms, tenant: sessionTenant, + tokenExpiration, }; + // provide token-expiration info to the service worker return localforage.setItem('loginResponse', data) - .then(() => localforage.setItem('okapiSess', okapiSess)) + .then(() => localforage.setItem(SESSION_NAME, okapiSess)) .then(() => { + store.dispatch(setIsAuthenticated(true)); store.dispatch(setSessionData(okapiSess)); return loadResources(okapiUrl, store, sessionTenant, user.id); }); } /** - * validateUser - * return a promise that fetches from bl-users/self. - * if successful, dispatch the result to create a session - * if not, clear the session and token. + * handleRtrError + * Clear out the redux store and logout. * - * @param {string} okapiUrl - * @param {redux store} store - * @param {string} tenant - * @param {object} session - * - * @returns {Promise} + * @param {*} event + * @param {*} store + * @returns void */ -export function validateUser(okapiUrl, store, tenant, session) { - const { token, user, perms, tenant: sessionTenant = tenant } = session; +export const handleRtrError = (event, store) => { + logger.log('rtr', 'rtr error; logging out', event.detail); + store.dispatch(setIsAuthenticated(false)); + store.dispatch(clearCurrentUser()); + store.dispatch(resetStore()); + localforage.removeItem(SESSION_NAME) + .then(localforage.removeItem('loginResponse')); +}; - return fetch(`${okapiUrl}/bl-users/_self`, { headers: getHeaders(sessionTenant, token) }).then((resp) => { - if (resp.ok) { - return resp.json().then((data) => { - store.dispatch(setLoginData(data)); - store.dispatch(setSessionData({ token, user, perms, tenant: sessionTenant })); - return loadResources(okapiUrl, store, sessionTenant, user.id); - }); - } else { - store.dispatch(clearCurrentUser()); - store.dispatch(clearOkapiToken()); - return localforage.removeItem('okapiSess'); - } - }).catch((error) => { - store.dispatch(setServerDown()); - return error; +/** + * addRtrEventListeners + * RTR_ERROR_EVENT: RTR error, logout + * RTR_ROTATION_EVENT: configure a timer for auto-logout + * + * @param {*} okapiConfig + * @param {*} store + */ +export function addRtrEventListeners(okapiConfig, store) { + document.addEventListener(RTR_ERROR_EVENT, (e) => { + handleRtrError(e, store); }); + + // document.addEventListener(RTR_ROTATION_EVENT, (e) => { + // handleRtrRotation(e, store); + // }); } /** @@ -502,7 +597,7 @@ function processSSOLoginResponse(resp) { * @returns {Promise} resolving to the response's JSON */ export function handleLoginError(dispatch, resp) { - return localforage.removeItem('okapiSess') + return localforage.removeItem(SESSION_NAME) .then(() => processBadResponse(dispatch, resp)) .then(responseBody => { dispatch(setOkapiReady()); @@ -518,7 +613,6 @@ export function handleLoginError(dispatch, resp) { * @param {redux store} store * @param {string} tenant * @param {Response} resp HTTP response - * @param {string} ssoToken * * @returns {Promise} resolving with login response body, rejecting with, ummmmm */ @@ -541,6 +635,62 @@ export function processOkapiSession(okapiUrl, store, tenant, resp, ssoToken) { } } +/** + * validateUser + * return a promise that fetches from bl-users/self. + * if successful, dispatch the result to create a session + * if not, clear the session and token. + * + * @param {string} okapiUrl + * @param {redux store} store + * @param {string} tenant + * @param {object} session + * + * @returns {Promise} + */ +export function validateUser(okapiUrl, store, tenant, session) { + const { token, user, perms, tenant: sessionTenant = tenant } = session; + return fetch(`${okapiUrl}/bl-users/_self`, { + headers: getHeaders(sessionTenant, token), + credentials: 'include', + mode: 'cors', + }).then((resp) => { + if (resp.ok) { + return resp.json().then((data) => { + // clear any auth-n errors + store.dispatch(setAuthError(null)); + store.dispatch(setLoginData(data)); + + // If the request succeeded, we know the AT must be valid, but the + // response body from this endpoint doesn't include token-expiration + // data. So ... we set a near-future RT and an already-expired AT. + // On the next request, the expired AT will prompt an RTR cycle and + // we'll get real expiration values then. + const tokenExpiration = { + atExpires: -1, + rtExpires: Date.now() + (10 * 60 * 1000), + }; + + store.dispatch(setSessionData({ + isAuthenticated: true, + user, + perms, + tenant: sessionTenant, + token, + tokenExpiration, + })); + return loadResources(okapiUrl, store, sessionTenant, user.id); + }); + } else { + return logout(okapiUrl, store); + } + }).catch((error) => { + console.error(error); // eslint-disable-line no-console + store.dispatch(setServerDown()); + return error; + }); +} + /** * checkOkapiSession * 1. Pull the session from local storage; if non-empty validate it, dispatching load-resources actions. @@ -552,7 +702,7 @@ export function processOkapiSession(okapiUrl, store, tenant, resp, ssoToken) { * @param {string} tenant */ export function checkOkapiSession(okapiUrl, store, tenant) { - localforage.getItem('okapiSess') + getOkapiSession() .then((sess) => { return sess !== null ? validateUser(okapiUrl, store, tenant, sess) : null; }) @@ -576,10 +726,13 @@ export function checkOkapiSession(okapiUrl, store, tenant) { * @returns {Promise} */ export function requestLogin(okapiUrl, store, tenant, data) { - return fetch(`${okapiUrl}/bl-users/login?expandPermissions=true&fullPermissions=true`, { - method: 'POST', - headers: { 'X-Okapi-Tenant': tenant, 'Content-Type': 'application/json' }, + const loginPath = config.useSecureTokens ? 'login-with-expiry' : 'login'; + return fetch(`${okapiUrl}/bl-users/${loginPath}?expandPermissions=true&fullPermissions=true`, { body: JSON.stringify(data), + credentials: 'include', + headers: { 'X-Okapi-Tenant': tenant, 'Content-Type': 'application/json' }, + method: 'POST', + mode: 'cors', }) .then(resp => processOkapiSession(okapiUrl, store, tenant, resp)); } @@ -589,7 +742,6 @@ export function requestLogin(okapiUrl, store, tenant, data) { * retrieve currently-authenticated user * @param {string} okapiUrl * @param {string} tenant - * @param {string} token * * @returns {Promise} Promise resolving to the response of the request */ @@ -606,7 +758,6 @@ function fetchUserWithPerms(okapiUrl, tenant, token) { * @param {string} okapiUrl * @param {redux store} store * @param {string} tenant - * @param {string} token * * @returns {Promise} Promise resolving to the response-body (JSON) of the request */ @@ -648,10 +799,10 @@ export function requestSSOLogin(okapiUrl, tenant) { * @returns {Promise} */ export function updateUser(store, data) { - return localforage.getItem('okapiSess') + return getOkapiSession() .then((sess) => { sess.user = { ...sess.user, ...data }; - return localforage.setItem('okapiSess', sess); + return localforage.setItem(SESSION_NAME, sess); }) .then(() => { store.dispatch(updateCurrentUser(data)); @@ -668,9 +819,9 @@ export function updateUser(store, data) { * @returns {Promise} */ export async function updateTenant(okapi, tenant) { - const okapiSess = await localforage.getItem('okapiSess'); + const okapiSess = await getOkapiSession(); const userWithPermsResponse = await fetchUserWithPerms(okapi.url, tenant, okapi.token); const userWithPerms = await userWithPermsResponse.json(); - await localforage.setItem('okapiSess', { ...okapiSess, tenant, ...spreadUserWithPerms(userWithPerms) }); + await localforage.setItem(SESSION_NAME, { ...okapiSess, tenant, ...spreadUserWithPerms(userWithPerms) }); } diff --git a/src/loginServices.test.js b/src/loginServices.test.js index 1c7f6a6f0..c1d2d95b5 100644 --- a/src/loginServices.test.js +++ b/src/loginServices.test.js @@ -1,18 +1,23 @@ import localforage from 'localforage'; import { - spreadUserWithPerms, createOkapiSession, + getOkapiSession, + getTokenExpiry, handleLoginError, loadTranslations, processOkapiSession, + setTokenExpiry, + spreadUserWithPerms, supportedLocales, supportedNumberingSystems, - updateUser, updateTenant, + updateUser, validateUser, } from './loginServices'; + + import { clearCurrentUser, setCurrentPerms, @@ -22,19 +27,35 @@ import { // setPlugins, // setBindings, // setTranslations, - clearOkapiToken, setAuthError, // checkSSO, + setIsAuthenticated, setOkapiReady, setServerDown, - setSessionData, + // setSessionData, + // setTokenExpiration, setLoginData, updateCurrentUser, } from './okapiActions'; import { defaultErrors } from './constants'; +// reassign console.log to keep things quiet +const consoleInterruptor = {}; +beforeAll(() => { + consoleInterruptor.log = global.console.log; + consoleInterruptor.error = global.console.error; + consoleInterruptor.warn = global.console.warn; + console.log = () => { }; + console.error = () => { }; + console.warn = () => { }; +}); +afterAll(() => { + global.console.log = consoleInterruptor.log; + global.console.error = consoleInterruptor.error; + global.console.warn = consoleInterruptor.warn; +}); jest.mock('localforage', () => ({ getItem: jest.fn(() => Promise.resolve({ user: {} })), @@ -64,7 +85,6 @@ const mockFetchCleanUp = () => { delete global.fetch; }; - describe('createOkapiSession', () => { it('clears authentication errors', async () => { const store = { @@ -76,19 +96,25 @@ describe('createOkapiSession', () => { }), }; + const te = { + accessTokenExpiration: '2023-11-06T18:05:33Z', + refreshTokenExpiration: '2023-10-30T18:15:33Z', + }; + const data = { user: { id: 'user-id', }, permissions: { permissions: [{ permissionName: 'a' }, { permissionName: 'b' }] - } + }, + tokenExpiration: te, }; const permissionsMap = { a: true, b: true }; - mockFetchSuccess([]); await createOkapiSession('url', store, 'tenant', 'token', data); + expect(store.dispatch).toHaveBeenCalledWith(setIsAuthenticated(true)); expect(store.dispatch).toHaveBeenCalledWith(setAuthError(null)); expect(store.dispatch).toHaveBeenCalledWith(setLoginData(data)); expect(store.dispatch).toHaveBeenCalledWith(setCurrentPerms(permissionsMap)); @@ -196,7 +222,7 @@ describe('processOkapiSession', () => { mockFetchSuccess(); - await processOkapiSession('url', store, 'tenant', resp, 'token'); + await processOkapiSession('url', store, 'tenant', resp); expect(store.dispatch).toHaveBeenCalledWith(setAuthError(null)); expect(store.dispatch).toHaveBeenCalledWith(setOkapiReady()); @@ -213,7 +239,7 @@ describe('processOkapiSession', () => { } }; - await processOkapiSession('url', store, 'tenant', resp, 'token'); + await processOkapiSession('url', store, 'tenant', resp); expect(store.dispatch).toHaveBeenCalledWith(setOkapiReady()); expect(store.dispatch).toHaveBeenCalledWith(setAuthError([defaultErrors.DEFAULT_LOGIN_CLIENT_ERROR])); @@ -254,20 +280,23 @@ describe('validateUser', () => { const tenant = 'tenant'; const data = { monkey: 'bagel' }; - const token = 'token'; const user = { id: 'id' }; const perms = []; const session = { - token, user, perms, }; mockFetchSuccess(data); + // set a fixed system time so date math is stable + const now = new Date('2023-10-30T19:34:56.000Z'); + jest.useFakeTimers().setSystemTime(now); + await validateUser('url', store, tenant, session); - expect(store.dispatch).toHaveBeenCalledWith(setLoginData(data)); - expect(store.dispatch).toHaveBeenCalledWith(setSessionData({ token, user, perms, tenant })); + + expect(store.dispatch).toHaveBeenNthCalledWith(1, setAuthError(null)); + expect(store.dispatch).toHaveBeenNthCalledWith(2, setLoginData(data)); mockFetchCleanUp(); }); @@ -280,11 +309,9 @@ describe('validateUser', () => { const tenant = 'tenant'; const sessionTenant = 'sessionTenant'; const data = { monkey: 'bagel' }; - const token = 'token'; const user = { id: 'id' }; const perms = []; const session = { - token, user, perms, tenant: sessionTenant, @@ -293,8 +320,8 @@ describe('validateUser', () => { mockFetchSuccess(data); await validateUser('url', store, tenant, session); - expect(store.dispatch).toHaveBeenCalledWith(setLoginData(data)); - expect(store.dispatch).toHaveBeenCalledWith(setSessionData({ token, user, perms, tenant: sessionTenant })); + expect(store.dispatch).toHaveBeenNthCalledWith(1, setAuthError(null)); + expect(store.dispatch).toHaveBeenNthCalledWith(2, setLoginData(data)); mockFetchCleanUp(); }); @@ -310,7 +337,6 @@ describe('validateUser', () => { await validateUser('url', store, 'tenant', {}); expect(store.dispatch).toHaveBeenCalledWith(clearCurrentUser()); - expect(store.dispatch).toHaveBeenCalledWith(clearOkapiToken()); mockFetchCleanUp(); }); }); @@ -356,3 +382,50 @@ describe('updateTenant', () => { }); }); }); + +describe('localforage session wrapper', () => { + it('getOkapiSession retrieves a session object', async () => { + const o = { user: {} }; + localforage.getItem = jest.fn(() => Promise.resolve(o)); + + const s = await getOkapiSession(); + expect(s).toMatchObject(o); + }); + + describe('getTokenExpiry', () => { + it('finds tokenExpiration', async () => { + const o = { tokenExpiration: { trinity: 'cowboy junkies' } }; + localforage.getItem = jest.fn(() => Promise.resolve(o)); + + const s = await getTokenExpiry(); + expect(s).toMatchObject(o.tokenExpiration); + }); + + it('handles missing tokenExpiration', async () => { + const o = { nobody: 'here but us chickens' }; + localforage.getItem = jest.fn(() => Promise.resolve(o)); + + const s = await getTokenExpiry(); + expect(s).toBeFalsy(); + }); + }); + + it('setTokenExpiry set', async () => { + const o = { + margo: 'timmins', + margot: 'margot with a t looks better', + also: 'i thought we were talking about margot robbie?', + tokenExpiration: 'time out of mind', + }; + localforage.getItem = () => Promise.resolve(o); + localforage.setItem = (k, v) => Promise.resolve(v); + + const te = { + trinity: 'cowboy junkies', + sweet: 'james', + }; + + const s = await setTokenExpiry(te); + expect(s).toMatchObject({ ...o, tokenExpiration: te }); + }); +}); diff --git a/src/mainActions.js b/src/mainActions.js index 91f063fbb..bc41df8d2 100644 --- a/src/mainActions.js +++ b/src/mainActions.js @@ -18,10 +18,6 @@ function destroyStore() { }; } -// We export a single named function rather than using a default -// export, to remain consistent with okapiActions.js -// -// eslint-disable-next-line import/prefer-default-export export { resetStore, destroyStore, diff --git a/src/okapiActions.js b/src/okapiActions.js index fe3bed7a1..7bc51fc7a 100644 --- a/src/okapiActions.js +++ b/src/okapiActions.js @@ -74,6 +74,13 @@ function clearOkapiToken() { }; } +function setIsAuthenticated(b) { + return { + type: 'SET_IS_AUTHENTICATED', + isAuthenticated: Boolean(b), + }; +} + function setAuthError(message) { return { type: 'SET_AUTH_FAILURE', @@ -128,6 +135,13 @@ function updateCurrentUser(data) { }; } +function setTokenExpiration(tokenExpiration) { + return { + type: 'SET_TOKEN_EXPIRATION', + tokenExpiration, + }; +} + export { checkSSO, clearCurrentUser, @@ -137,6 +151,7 @@ export { setCurrency, setCurrentPerms, setCurrentUser, + setIsAuthenticated, setLocale, setLoginData, setOkapiReady, @@ -146,6 +161,7 @@ export { setSessionData, setSinglePlugin, setTimezone, + setTokenExpiration, setTranslations, updateCurrentUser, }; diff --git a/src/okapiActions.test.js b/src/okapiActions.test.js index 2376aed7e..9ac82f56d 100644 --- a/src/okapiActions.test.js +++ b/src/okapiActions.test.js @@ -1,8 +1,23 @@ import { + setIsAuthenticated, setLoginData, updateCurrentUser, } from './okapiActions'; +describe('setIsAuthenticated', () => { + it('handles truthy values', () => { + expect(setIsAuthenticated('truthy').isAuthenticated).toBe(true); + expect(setIsAuthenticated(1).isAuthenticated).toBe(true); + expect(setIsAuthenticated(true).isAuthenticated).toBe(true); + }); + + it('handles falsey values', () => { + expect(setIsAuthenticated('').isAuthenticated).toBe(false); + expect(setIsAuthenticated(0).isAuthenticated).toBe(false); + expect(setIsAuthenticated(false).isAuthenticated).toBe(false); + }); +}); + describe('setLoginData', () => { it('receives given data in "loginData"', () => { const av = { monkey: 'bagel' }; diff --git a/src/okapiReducer.js b/src/okapiReducer.js index aaa34563f..b88525a13 100644 --- a/src/okapiReducer.js +++ b/src/okapiReducer.js @@ -6,6 +6,8 @@ export default function okapiReducer(state = {}, action) { return Object.assign({}, state, { token: null }); case 'SET_CURRENT_USER': return Object.assign({}, state, { currentUser: action.currentUser }); + case 'SET_IS_AUTHENTICATED': + return Object.assign({}, state, { isAuthenticated: action.isAuthenticated }); case 'SET_LOCALE': return Object.assign({}, state, { locale: action.locale }); case 'SET_TIMEZONE': @@ -22,13 +24,15 @@ export default function okapiReducer(state = {}, action) { return Object.assign({}, state, { currentPerms: action.currentPerms }); case 'SET_LOGIN_DATA': return Object.assign({}, state, { loginData: action.loginData }); + case 'SET_TOKEN_EXPIRATION': + return Object.assign({}, state, { loginData: { ...state.loginData, tokenExpiration: action.tokenExpiration } }); case 'CLEAR_CURRENT_USER': return Object.assign({}, state, { currentUser: {}, currentPerms: {} }); case 'SET_SESSION_DATA': { - const { perms, user, token, tenant } = action.session; + const { isAuthenticated, perms, tenant, token, user } = action.session; const sessionTenant = tenant || state.tenant; - return { ...state, currentUser: user, currentPerms: perms, token, tenant: sessionTenant }; + return { ...state, currentUser: user, currentPerms: perms, isAuthenticated, tenant: sessionTenant, token }; } case 'SET_AUTH_FAILURE': return Object.assign({}, state, { authFailure: action.message }); diff --git a/src/okapiReducer.test.js b/src/okapiReducer.test.js index fc67ace6e..de9cd2827 100644 --- a/src/okapiReducer.test.js +++ b/src/okapiReducer.test.js @@ -1,6 +1,12 @@ import okapiReducer from './okapiReducer'; describe('okapiReducer', () => { + it('SET_IS_AUTHENTICATED', () => { + const isAuthenticated = true; + const o = okapiReducer({}, { type: 'SET_IS_AUTHENTICATED', isAuthenticated: true }); + expect(o).toMatchObject({ isAuthenticated }); + }); + it('SET_LOGIN_DATA', () => { const loginData = 'loginData'; const o = okapiReducer({}, { type: 'SET_LOGIN_DATA', loginData }); @@ -18,7 +24,6 @@ describe('okapiReducer', () => { const initialState = { perms: [], user: {}, - token: 'qwerty', tenant: 'central', }; const session = { @@ -29,7 +34,6 @@ describe('okapiReducer', () => { username: 'admin', } }, - token: 'ytrewq', tenant: 'institutional', }; const o = okapiReducer(initialState, { type: 'SET_SESSION_DATA', session }); diff --git a/src/queries/useConfigurations.test.js b/src/queries/useConfigurations.test.js index 83baeef4b..a40725cff 100644 --- a/src/queries/useConfigurations.test.js +++ b/src/queries/useConfigurations.test.js @@ -11,6 +11,20 @@ import useOkapiKy from '../useOkapiKy'; jest.mock('../useOkapiKy'); jest.mock('../StripesContext'); +// reassign console.log to keep things quiet +const consoleInterruptor = {}; +beforeAll(() => { + consoleInterruptor.log = global.console.log; + consoleInterruptor.error = global.console.error; + console.log = () => { }; + console.error = () => { }; +}); + +afterAll(() => { + global.console.log = consoleInterruptor.log; + global.console.error = consoleInterruptor.error; +}); + // set query retries to false. otherwise, react-query will thoughtfully // (but unhelpfully, in the context of testing) retry a failed query // several times causing the test to timeout when what we really want diff --git a/src/queries/useOkapiEnv.test.js b/src/queries/useOkapiEnv.test.js index 3101a000c..28efc91cf 100644 --- a/src/queries/useOkapiEnv.test.js +++ b/src/queries/useOkapiEnv.test.js @@ -11,6 +11,20 @@ import useOkapiKy from '../useOkapiKy'; jest.mock('../useOkapiKy'); jest.mock('../StripesContext'); +// reassign console.log to keep things quiet +const consoleInterruptor = {}; +beforeAll(() => { + consoleInterruptor.log = global.console.log; + consoleInterruptor.error = global.console.error; + console.log = () => { }; + console.error = () => { }; +}); + +afterAll(() => { + global.console.log = consoleInterruptor.log; + global.console.error = consoleInterruptor.error; +}); + // set query retries to false. otherwise, react-query will thoughtfully // (but unhelpfully, in the context of testing) retry a failed query // several times causing the test to timeout when what we really want diff --git a/src/service-worker.js b/src/service-worker.js index 608a6551c..1a7b8fcb0 100644 --- a/src/service-worker.js +++ b/src/service-worker.js @@ -1 +1 @@ -export default () => 'future of libraries is open'; +export default () => ('future of libraries is open'); diff --git a/src/serviceWorkerRegistration.js b/src/serviceWorkerRegistration.js index 91edc28cf..5f43165f5 100644 --- a/src/serviceWorkerRegistration.js +++ b/src/serviceWorkerRegistration.js @@ -1,12 +1,11 @@ -export const registerServiceWorker = async () => { }; +export const registerServiceWorker = async () => {}; export const unregisterServiceWorker = async () => { - console.log('-- (rtr) unregistering service worker ...'); // eslint-disable-line no-console + console.log('unregister'); // eslint-disable-line no-console if ('serviceWorker' in navigator) { navigator.serviceWorker.ready .then((reg) => { reg.unregister(); - console.log('-- (rtr) ... unregistered!'); // eslint-disable-line no-console }) .catch((error) => { console.error(error.message); // eslint-disable-line no-console diff --git a/src/useOkapiKy.js b/src/useOkapiKy.js index 22fdef09e..98f55e548 100644 --- a/src/useOkapiKy.js +++ b/src/useOkapiKy.js @@ -5,16 +5,20 @@ export default ({ tenant } = {}) => { const { locale = 'en', timeout = 30000, tenant: currentTenant, token, url } = useStripes().okapi; return ky.create({ - prefixUrl: url, + credentials: 'include', hooks: { beforeRequest: [ request => { request.headers.set('Accept-Language', locale); request.headers.set('X-Okapi-Tenant', tenant ?? currentTenant); - request.headers.set('X-Okapi-Token', token); + if (token) { + request.headers.set('X-Okapi-Token', token); + } } ] }, + mode: 'cors', + prefixUrl: url, retry: 0, timeout, }); diff --git a/src/useOkapiKy.test.js b/src/useOkapiKy.test.js index 97ceee978..9aa49acbc 100644 --- a/src/useOkapiKy.test.js +++ b/src/useOkapiKy.test.js @@ -14,7 +14,6 @@ describe('useOkapiKy', () => { locale: 'klingon', tenant: 'tenant', timeout: 271828, - token: 'token', url: 'https://whatever.com' }; @@ -35,7 +34,6 @@ describe('useOkapiKy', () => { expect(r.headers.set).toHaveBeenCalledWith('Accept-Language', okapi.locale); expect(r.headers.set).toHaveBeenCalledWith('X-Okapi-Tenant', okapi.tenant); - expect(r.headers.set).toHaveBeenCalledWith('X-Okapi-Token', okapi.token); }); it('provides default values if stripes lacks them', async () => { @@ -62,7 +60,6 @@ describe('useOkapiKy', () => { const okapi = { tenant: 'tenant', timeout: 271828, - token: 'token', url: 'https://whatever.com' }; diff --git a/src/withOkapiKy.js b/src/withOkapiKy.js index 522ab6056..49094afee 100644 --- a/src/withOkapiKy.js +++ b/src/withOkapiKy.js @@ -8,8 +8,10 @@ const withOkapiKy = (WrappedComponent) => { static propTypes = { stripes: PropTypes.shape({ okapi: PropTypes.shape({ + locale: PropTypes.string, tenant: PropTypes.string.isRequired, - token: PropTypes.string.isRequired, + timeout: PropTypes.number, + token: PropTypes.string, url: PropTypes.string.isRequired, }).isRequired, }).isRequired, @@ -17,17 +19,24 @@ const withOkapiKy = (WrappedComponent) => { constructor(props) { super(); - const { tenant, token, url } = props.stripes.okapi; + const { tenant, token, url, timeout = 30000, locale = 'en' } = props.stripes.okapi; this.okapiKy = ky.create({ - prefixUrl: url, + credentials: 'include', hooks: { beforeRequest: [ request => { + request.headers.set('Accept-Language', locale); request.headers.set('X-Okapi-Tenant', tenant); - request.headers.set('X-Okapi-Token', token); + if (token) { + request.headers.set('X-Okapi-Token', token); + } } ] - } + }, + mode: 'cors', + prefixUrl: url, + retry: 0, + timeout, }); } diff --git a/test/bigtest/helpers/setup-application.js b/test/bigtest/helpers/setup-application.js index 3f2121b9d..d2dd67a4e 100644 --- a/test/bigtest/helpers/setup-application.js +++ b/test/bigtest/helpers/setup-application.js @@ -41,7 +41,6 @@ export default function setupApplication({ // when auth is disabled, add a fake user to the store if (disableAuth) { initialState.okapi = { - token: 'test', currentUser: assign({ id: 'test', username: 'testuser', @@ -51,7 +50,8 @@ export default function setupApplication({ addresses: [], servicePoints: [] }, currentUser), - currentPerms: permissions + currentPerms: permissions, + isAuthenticated: true, }; } else { initialState.okapi = { @@ -74,9 +74,14 @@ export default function setupApplication({ if (userLoggedIn) { localforage.setItem('okapiSess', { - token: initialState.okapi.token, + isAuthenticated: true, user: initialState.okapi.currentUser, perms: initialState.okapi.currentPerms, + tenant: 'tenant', + tokenExpiration: { + atExpires: Date.now() + (10 * 60 * 1000), + rtExpires: Date.now() + (10 * 60 * 1000), + }, }); } diff --git a/test/bigtest/network/config.js b/test/bigtest/network/config.js index 82e58f915..72f9afc46 100644 --- a/test/bigtest/network/config.js +++ b/test/bigtest/network/config.js @@ -29,6 +29,13 @@ export default function configure() { launchDescriptor : {} }]); + this.get('/service-worker.js', { + monkey: 'bagel' + }); + this.get('/_/env', { + monkey: 'bagel' + }); + this.get('/saml/check', { ssoEnabled: false }); @@ -43,11 +50,27 @@ export default function configure() { }); this.post('/bl-users/password-reset/reset', {}, 401); + this.post('/authn/logout', {}, 204); this.post('/bl-users/login', () => { - return new Response(201, { - 'X-Okapi-Token': `myOkapiToken:${Date.now()}` - }, { + return new Response(201, {}, { + user: { + id: 'test', + username: 'testuser', + personal: { + lastName: 'User', + firstName: 'Test', + email: 'user@folio.org', + } + }, + permissions: { + permissions: [] + } + }); + }); + + this.post('/bl-users/login-with-expiry', () => { + return new Response(201, {}, { user: { id: 'test', username: 'testuser', diff --git a/test/bigtest/tests/session-timeout-test.js b/test/bigtest/tests/session-timeout-test.js index 702f2a1b5..f6e8046e4 100644 --- a/test/bigtest/tests/session-timeout-test.js +++ b/test/bigtest/tests/session-timeout-test.js @@ -5,7 +5,7 @@ import setupApplication from '../helpers/setup-core-application'; import LoginInteractor from '../interactors/login'; import translations from '../../../translations/stripes-core/en'; -describe('Session timeout test', () => { +describe.skip('Session timeout test', () => { const login = new LoginInteractor('form[class^="form--"]'); setupApplication({ diff --git a/test/jest/setupFiles.js b/test/jest/setupFiles.js index 8952b0c81..6ddbfa005 100644 --- a/test/jest/setupFiles.js +++ b/test/jest/setupFiles.js @@ -1,5 +1,7 @@ +import 'regenerator-runtime/runtime'; +import { enableFetchMocks } from 'jest-fetch-mock'; + // See https://github.com/facebook/jest/issues/335#issuecomment-703691592 import './__mock__'; -import 'regenerator-runtime/runtime'; - +enableFetchMocks(); diff --git a/translations/stripes-core/en.json b/translations/stripes-core/en.json index 3aa4b1696..a543baa28 100644 --- a/translations/stripes-core/en.json +++ b/translations/stripes-core/en.json @@ -11,6 +11,7 @@ "title.checkEmail": "Check your email", "title.changePassword": "Change password", "title.noPermission": "No permission", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again.", "front.welcome": "Welcome, the Future Of Libraries Is OPEN!", "front.home": "Home", diff --git a/translations/stripes-core/en_GB.json b/translations/stripes-core/en_GB.json index 1bb3ed457..6c92ae72e 100644 --- a/translations/stripes-core/en_GB.json +++ b/translations/stripes-core/en_GB.json @@ -64,6 +64,7 @@ "title.forgotUsername": "Forgot username?", "title.checkEmail": "Check your email", "title.changePassword": "Change password", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again.", "button.hidePassword": "Hide password", "button.showPassword": "Show password", "button.forgotPassword": "Forgot password?", diff --git a/translations/stripes-core/en_SE.json b/translations/stripes-core/en_SE.json index 1bb3ed457..6c92ae72e 100644 --- a/translations/stripes-core/en_SE.json +++ b/translations/stripes-core/en_SE.json @@ -64,6 +64,7 @@ "title.forgotUsername": "Forgot username?", "title.checkEmail": "Check your email", "title.changePassword": "Change password", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again.", "button.hidePassword": "Hide password", "button.showPassword": "Show password", "button.forgotPassword": "Forgot password?", diff --git a/translations/stripes-core/en_US.json b/translations/stripes-core/en_US.json index 1bb3ed457..6c92ae72e 100644 --- a/translations/stripes-core/en_US.json +++ b/translations/stripes-core/en_US.json @@ -64,6 +64,7 @@ "title.forgotUsername": "Forgot username?", "title.checkEmail": "Check your email", "title.changePassword": "Change password", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again.", "button.hidePassword": "Hide password", "button.showPassword": "Show password", "button.forgotPassword": "Forgot password?", diff --git a/yarn.lock b/yarn.lock index 74a8c8174..13899dee1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4394,6 +4394,13 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" +cross-fetch@^3.0.4: + version "3.1.8" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82" + integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -7493,6 +7500,14 @@ jest-environment-node@^29.7.0: jest-mock "^29.7.0" jest-util "^29.7.0" +jest-fetch-mock@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== + dependencies: + cross-fetch "^3.0.4" + promise-polyfill "^8.1.3" + jest-get-type@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" @@ -8841,7 +8856,7 @@ node-addon-api@^6.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== -node-fetch@^2.6.7: +node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -9447,7 +9462,7 @@ postcss-calc@^9.0.1: postcss-selector-parser "^6.0.11" postcss-value-parser "^4.2.0" -"postcss-color-function@github:folio-org/postcss-color-function": +postcss-color-function@folio-org/postcss-color-function: version "4.1.0" resolved "https://codeload.github.com/folio-org/postcss-color-function/tar.gz/c128aad740ae740fb571c4b6493f467dd51efe85" dependencies: @@ -9661,6 +9676,11 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-polyfill@^8.1.3: + version "8.3.0" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63" + integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" From 578b3f0957b7cb6b404575d1f2fc669e3128e98c Mon Sep 17 00:00:00 2001 From: FOLIO Translations Bot <38661258+folio-translations@users.noreply.github.com> Date: Tue, 16 Jan 2024 17:34:37 -0500 Subject: [PATCH 3/4] Lokalise: updates --- translations/stripes-core/ar.json | 3 ++- translations/stripes-core/ber.json | 3 ++- translations/stripes-core/ca.json | 3 ++- translations/stripes-core/cs_CZ.json | 3 ++- translations/stripes-core/da.json | 3 ++- translations/stripes-core/de.json | 3 ++- translations/stripes-core/en_GB.json | 4 ++-- translations/stripes-core/en_SE.json | 4 ++-- translations/stripes-core/en_US.json | 4 ++-- translations/stripes-core/es.json | 3 ++- translations/stripes-core/es_419.json | 3 ++- translations/stripes-core/es_ES.json | 3 ++- translations/stripes-core/fr.json | 3 ++- translations/stripes-core/fr_FR.json | 3 ++- translations/stripes-core/he.json | 3 ++- translations/stripes-core/hi_IN.json | 3 ++- translations/stripes-core/hu.json | 3 ++- translations/stripes-core/it_IT.json | 3 ++- translations/stripes-core/ja.json | 3 ++- translations/stripes-core/ko.json | 3 ++- translations/stripes-core/nb.json | 3 ++- translations/stripes-core/nn.json | 3 ++- translations/stripes-core/pl.json | 3 ++- translations/stripes-core/pt_BR.json | 3 ++- translations/stripes-core/pt_PT.json | 3 ++- translations/stripes-core/ru.json | 3 ++- translations/stripes-core/sk.json | 3 ++- translations/stripes-core/sv.json | 3 ++- translations/stripes-core/ur.json | 3 ++- translations/stripes-core/zh_CN.json | 3 ++- translations/stripes-core/zh_TW.json | 3 ++- 31 files changed, 62 insertions(+), 34 deletions(-) diff --git a/translations/stripes-core/ar.json b/translations/stripes-core/ar.json index f391d59d2..7d1f84b11 100644 --- a/translations/stripes-core/ar.json +++ b/translations/stripes-core/ar.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/ber.json b/translations/stripes-core/ber.json index 1bb3ed457..70514b854 100644 --- a/translations/stripes-core/ber.json +++ b/translations/stripes-core/ber.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/ca.json b/translations/stripes-core/ca.json index 4ae6c4e42..114cbac7b 100644 --- a/translations/stripes-core/ca.json +++ b/translations/stripes-core/ca.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/cs_CZ.json b/translations/stripes-core/cs_CZ.json index 634699499..ce7d92b7d 100644 --- a/translations/stripes-core/cs_CZ.json +++ b/translations/stripes-core/cs_CZ.json @@ -144,5 +144,6 @@ "stale.warning": "Aplikace se na serveru změnila a je třeba ji obnovit.", "stale.reload": "Klikněte zde pro opětovné načtení.", "placeholder.forgotPassword": "Zadejte e-mail nebo telefon", - "placeholder.forgotUsername": "Zadejte e-mail nebo telefon" + "placeholder.forgotUsername": "Zadejte e-mail nebo telefon", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/da.json b/translations/stripes-core/da.json index 4a13b1e5b..8d5a31b7f 100644 --- a/translations/stripes-core/da.json +++ b/translations/stripes-core/da.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/de.json b/translations/stripes-core/de.json index 535e5764c..91f9b3bfe 100644 --- a/translations/stripes-core/de.json +++ b/translations/stripes-core/de.json @@ -144,5 +144,6 @@ "stale.warning": "Die Anwendung hat sich auf dem Server geändert und muss neu geladen werden.", "stale.reload": "Zum Neuladen hier klicken.", "placeholder.forgotPassword": "E-Mail oder Telefonnummer eingeben", - "placeholder.forgotUsername": "E-Mail oder Telefonnummer eingeben" + "placeholder.forgotUsername": "E-Mail oder Telefonnummer eingeben", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/en_GB.json b/translations/stripes-core/en_GB.json index 6c92ae72e..70514b854 100644 --- a/translations/stripes-core/en_GB.json +++ b/translations/stripes-core/en_GB.json @@ -64,7 +64,6 @@ "title.forgotUsername": "Forgot username?", "title.checkEmail": "Check your email", "title.changePassword": "Change password", - "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again.", "button.hidePassword": "Hide password", "button.showPassword": "Show password", "button.forgotPassword": "Forgot password?", @@ -145,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/en_SE.json b/translations/stripes-core/en_SE.json index 6c92ae72e..70514b854 100644 --- a/translations/stripes-core/en_SE.json +++ b/translations/stripes-core/en_SE.json @@ -64,7 +64,6 @@ "title.forgotUsername": "Forgot username?", "title.checkEmail": "Check your email", "title.changePassword": "Change password", - "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again.", "button.hidePassword": "Hide password", "button.showPassword": "Show password", "button.forgotPassword": "Forgot password?", @@ -145,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/en_US.json b/translations/stripes-core/en_US.json index 6c92ae72e..70514b854 100644 --- a/translations/stripes-core/en_US.json +++ b/translations/stripes-core/en_US.json @@ -64,7 +64,6 @@ "title.forgotUsername": "Forgot username?", "title.checkEmail": "Check your email", "title.changePassword": "Change password", - "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again.", "button.hidePassword": "Hide password", "button.showPassword": "Show password", "button.forgotPassword": "Forgot password?", @@ -145,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/es.json b/translations/stripes-core/es.json index d3df79064..c5c0c0a03 100644 --- a/translations/stripes-core/es.json +++ b/translations/stripes-core/es.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/es_419.json b/translations/stripes-core/es_419.json index 0976183af..5c787d900 100644 --- a/translations/stripes-core/es_419.json +++ b/translations/stripes-core/es_419.json @@ -144,5 +144,6 @@ "stale.warning": "La aplicación ha cambiado en el servidor y necesita ser actualizada.", "stale.reload": "Haga clic aquí para recargar.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Introduce email o teléfono" + "placeholder.forgotUsername": "Introduce email o teléfono", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/es_ES.json b/translations/stripes-core/es_ES.json index a77b17945..0d7475b46 100644 --- a/translations/stripes-core/es_ES.json +++ b/translations/stripes-core/es_ES.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Introducir correo electrónico o teléfono", - "placeholder.forgotUsername": "Introducir correo electrónico o teléfono" + "placeholder.forgotUsername": "Introducir correo electrónico o teléfono", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/fr.json b/translations/stripes-core/fr.json index 85b98b24f..6030b5979 100644 --- a/translations/stripes-core/fr.json +++ b/translations/stripes-core/fr.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/fr_FR.json b/translations/stripes-core/fr_FR.json index cac00c226..957681504 100644 --- a/translations/stripes-core/fr_FR.json +++ b/translations/stripes-core/fr_FR.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/he.json b/translations/stripes-core/he.json index 7e8fbbb21..1e4d3a733 100644 --- a/translations/stripes-core/he.json +++ b/translations/stripes-core/he.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/hi_IN.json b/translations/stripes-core/hi_IN.json index 1bb3ed457..70514b854 100644 --- a/translations/stripes-core/hi_IN.json +++ b/translations/stripes-core/hi_IN.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/hu.json b/translations/stripes-core/hu.json index 19dfb45c9..cbff3c6a0 100644 --- a/translations/stripes-core/hu.json +++ b/translations/stripes-core/hu.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/it_IT.json b/translations/stripes-core/it_IT.json index 9b95d8df3..95922f013 100644 --- a/translations/stripes-core/it_IT.json +++ b/translations/stripes-core/it_IT.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/ja.json b/translations/stripes-core/ja.json index 72bb94953..54b719351 100644 --- a/translations/stripes-core/ja.json +++ b/translations/stripes-core/ja.json @@ -144,5 +144,6 @@ "stale.warning": "サーバー上でアプリケーションが変更されたため、更新する必要があります。", "stale.reload": "ここをクリックしてリロードしてください。", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/ko.json b/translations/stripes-core/ko.json index 11797985b..77cd23b61 100644 --- a/translations/stripes-core/ko.json +++ b/translations/stripes-core/ko.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/nb.json b/translations/stripes-core/nb.json index 1bb3ed457..70514b854 100644 --- a/translations/stripes-core/nb.json +++ b/translations/stripes-core/nb.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/nn.json b/translations/stripes-core/nn.json index 1bb3ed457..70514b854 100644 --- a/translations/stripes-core/nn.json +++ b/translations/stripes-core/nn.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/pl.json b/translations/stripes-core/pl.json index 4451ac598..a470d764f 100644 --- a/translations/stripes-core/pl.json +++ b/translations/stripes-core/pl.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/pt_BR.json b/translations/stripes-core/pt_BR.json index 8a08b4bfa..dfe40e6d3 100644 --- a/translations/stripes-core/pt_BR.json +++ b/translations/stripes-core/pt_BR.json @@ -144,5 +144,6 @@ "stale.warning": "O aplicativo foi alterado no servidor e precisa ser atualizado.", "stale.reload": "Clique aqui para recarregar.", "placeholder.forgotPassword": "Digite e-mail ou telefone", - "placeholder.forgotUsername": "Digite e-mail ou telefone" + "placeholder.forgotUsername": "Digite e-mail ou telefone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/pt_PT.json b/translations/stripes-core/pt_PT.json index 149cb07f4..517acbceb 100644 --- a/translations/stripes-core/pt_PT.json +++ b/translations/stripes-core/pt_PT.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/ru.json b/translations/stripes-core/ru.json index 9034e22c2..82ffb6394 100644 --- a/translations/stripes-core/ru.json +++ b/translations/stripes-core/ru.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/sk.json b/translations/stripes-core/sk.json index bbecc3de4..ffccddd09 100644 --- a/translations/stripes-core/sk.json +++ b/translations/stripes-core/sk.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/sv.json b/translations/stripes-core/sv.json index 1bb3ed457..70514b854 100644 --- a/translations/stripes-core/sv.json +++ b/translations/stripes-core/sv.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/ur.json b/translations/stripes-core/ur.json index 1bb3ed457..70514b854 100644 --- a/translations/stripes-core/ur.json +++ b/translations/stripes-core/ur.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/zh_CN.json b/translations/stripes-core/zh_CN.json index d2e04b3bb..5c829895c 100644 --- a/translations/stripes-core/zh_CN.json +++ b/translations/stripes-core/zh_CN.json @@ -144,5 +144,6 @@ "stale.warning": "应用程序在服务器上发生了变化,需要刷新。", "stale.reload": "单击此处重新加载。", "placeholder.forgotPassword": "输入电子邮件或电话", - "placeholder.forgotUsername": "输入电子邮件或电话" + "placeholder.forgotUsername": "输入电子邮件或电话", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file diff --git a/translations/stripes-core/zh_TW.json b/translations/stripes-core/zh_TW.json index 477b5b653..efded7673 100644 --- a/translations/stripes-core/zh_TW.json +++ b/translations/stripes-core/zh_TW.json @@ -144,5 +144,6 @@ "stale.warning": "The application has changed on the server and needs to be refreshed.", "stale.reload": "Click here to reload.", "placeholder.forgotPassword": "Enter email or phone", - "placeholder.forgotUsername": "Enter email or phone" + "placeholder.forgotUsername": "Enter email or phone", + "title.cookieEnabled": "Cookies are required to login. Please enable cookies and try again." } \ No newline at end of file From 35799e893db85bdb17b87659565d7c5bbc3f8bcc Mon Sep 17 00:00:00 2001 From: UladzislauKutarkin <72550466+UladzislauKutarkin@users.noreply.github.com> Date: Wed, 17 Jan 2024 11:55:17 +0400 Subject: [PATCH 4/4] STCOR-753: Add aria-label for `ProfileDropdown.js` (#1382) --- CHANGELOG.md | 1 + src/components/MainNav/AppList/AppList.js | 3 +-- .../MainNav/ProfileDropdown/ProfileDropdown.js | 13 +++++++++++-- translations/stripes-core/en.json | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b56076101..5378cb31f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * Export `getEventHandler` to be able to create events in other modules. Refs STCOR-770. * Opt-in: handle access-control via cookies. Refs STCOR-671. * Opt-in: disable login when cookies are disabled. Refs STCOR-762. +* Add arial-label for `ProfileDropdown.js`. Refs STCOR-753. ## [10.0.0](https://github.com/folio-org/stripes-core/tree/v10.0.0) (2023-10-11) [Full Changelog](https://github.com/folio-org/stripes-core/compare/v9.0.0...v10.0.0) diff --git a/src/components/MainNav/AppList/AppList.js b/src/components/MainNav/AppList/AppList.js index fb45f917a..0cbe9def2 100644 --- a/src/components/MainNav/AppList/AppList.js +++ b/src/components/MainNav/AppList/AppList.js @@ -122,7 +122,7 @@ class AppList extends Component { * The button that toggles the dropdown */ renderDropdownToggleButton = ({ open, getTriggerProps }) => { - const { dropdownToggleId, intl: { formatMessage } } = this.props; + const { dropdownToggleId } = this.props; const icon = ( @@ -138,7 +138,6 @@ class AppList extends Component { { - const { intl } = this.props; + const { intl, stripes: { okapi } } = this.props; + const userData = this.getUserData(); + const servicePointName = userData?.curServicePoint?.name; + const tenantName = userData?.tenants?.find(({ id }) => id === okapi.tenant)?.name; return (