From 905a0a108302148a1f4ac46cc5b128ec290f3d75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 09:04:45 -0600 Subject: [PATCH 1/4] build(deps-dev): bump vitest and @vitest/coverage-v8 in /client (#1053) Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) and [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8). These dependencies needed to be updated together. Updates `vitest` from 2.1.2 to 2.1.9 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.9/packages/vitest) Updates `@vitest/coverage-v8` from 2.1.2 to 2.1.9 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.9/packages/coverage-v8) --- updated-dependencies: - dependency-name: vitest dependency-type: direct:development - dependency-name: "@vitest/coverage-v8" dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Chandra Y --- client/package-lock.json | 247 ++++++++++++++++++++++----------------- client/package.json | 4 +- 2 files changed, 143 insertions(+), 108 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index d192fc480..7090aefca 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -54,7 +54,7 @@ "@types/react-redux": "^7.1.18", "@typescript-eslint/parser": "^8.8.0", "@vitejs/plugin-react": "^4.3.2", - "@vitest/coverage-v8": "^2.1.1", + "@vitest/coverage-v8": "^2.1.9", "eslint": "^8.57.1", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.26.1", @@ -76,7 +76,7 @@ "timekeeper": "^2.3.1", "typescript": "5.5", "vite": "^5.4.14", - "vitest": "^2.1.1", + "vitest": "^2.1.9", "weak-key": "^1.0.1" } }, @@ -5199,21 +5199,22 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.2.tgz", - "integrity": "sha512-b7kHrFrs2urS0cOk5N10lttI8UdJ/yP3nB4JYTREvR5o18cR99yPpK4gK8oQgI42BVv0ILWYUSYB7AXkAUDc0g==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", + "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^0.2.3", - "debug": "^4.3.6", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.11", - "magicast": "^0.3.4", - "std-env": "^3.7.0", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", "test-exclude": "^7.0.1", "tinyrainbow": "^1.2.0" }, @@ -5221,8 +5222,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.2", - "vitest": "2.1.2" + "@vitest/browser": "2.1.9", + "vitest": "2.1.9" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -5231,14 +5232,15 @@ } }, "node_modules/@vitest/expect": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz", - "integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.2", - "@vitest/utils": "2.1.2", - "chai": "^5.1.1", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -5246,21 +5248,21 @@ } }, "node_modules/@vitest/mocker": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz", - "integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/spy": "^2.1.0-beta.1", + "@vitest/spy": "2.1.9", "estree-walker": "^3.0.3", - "magic-string": "^0.30.11" + "magic-string": "^0.30.12" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/spy": "2.1.2", - "msw": "^2.3.5", + "msw": "^2.4.9", "vite": "^5.0.0" }, "peerDependenciesMeta": { @@ -5277,15 +5279,17 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz", - "integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", "dev": true, + "license": "MIT", "dependencies": { "tinyrainbow": "^1.2.0" }, @@ -5294,12 +5298,13 @@ } }, "node_modules/@vitest/runner": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz", - "integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.2", + "@vitest/utils": "2.1.9", "pathe": "^1.1.2" }, "funding": { @@ -5307,13 +5312,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz", - "integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.2", - "magic-string": "^0.30.11", + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", "pathe": "^1.1.2" }, "funding": { @@ -5321,25 +5327,27 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz", - "integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", "dev": true, + "license": "MIT", "dependencies": { - "tinyspy": "^3.0.0" + "tinyspy": "^3.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz", - "integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.2", - "loupe": "^3.1.1", + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, "funding": { @@ -5666,6 +5674,7 @@ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" } @@ -6116,6 +6125,7 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6217,10 +6227,11 @@ ] }, "node_modules/chai": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", - "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", "dev": true, + "license": "MIT", "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", @@ -6267,6 +6278,7 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 16" } @@ -6956,6 +6968,7 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -7410,6 +7423,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -8068,6 +8088,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8472,15 +8502,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -12637,13 +12658,11 @@ } }, "node_modules/loupe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", - "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } + "license": "MIT" }, "node_modules/lru-cache": { "version": "5.1.1", @@ -12665,10 +12684,11 @@ } }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } @@ -13393,13 +13413,15 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pathval": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14.16" } @@ -16266,7 +16288,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/signal-exit": { "version": "3.0.7", @@ -16454,13 +16477,15 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", - "dev": true + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true, + "license": "MIT" }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", @@ -17116,19 +17141,22 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz", - "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==", - "dev": true + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" }, "node_modules/tinypool": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz", - "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" } @@ -17138,6 +17166,7 @@ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -17147,6 +17176,7 @@ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.0.0" } @@ -17597,13 +17627,15 @@ } }, "node_modules/vite-node": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz", - "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", "dev": true, + "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.6", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", "vite": "^5.0.0" }, @@ -17653,29 +17685,31 @@ } }, "node_modules/vitest": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz", - "integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==", - "dev": true, - "dependencies": { - "@vitest/expect": "2.1.2", - "@vitest/mocker": "2.1.2", - "@vitest/pretty-format": "^2.1.2", - "@vitest/runner": "2.1.2", - "@vitest/snapshot": "2.1.2", - "@vitest/spy": "2.1.2", - "@vitest/utils": "2.1.2", - "chai": "^5.1.1", - "debug": "^4.3.6", - "magic-string": "^0.30.11", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", "pathe": "^1.1.2", - "std-env": "^3.7.0", + "std-env": "^3.8.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.0", - "tinypool": "^1.0.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.1.2", + "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "bin": { @@ -17690,8 +17724,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.1.2", - "@vitest/ui": "2.1.2", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, @@ -17904,6 +17938,7 @@ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" diff --git a/client/package.json b/client/package.json index 895516cb3..ae12d6230 100644 --- a/client/package.json +++ b/client/package.json @@ -77,7 +77,7 @@ "@types/react-redux": "^7.1.18", "@typescript-eslint/parser": "^8.8.0", "@vitejs/plugin-react": "^4.3.2", - "@vitest/coverage-v8": "^2.1.1", + "@vitest/coverage-v8": "^2.1.9", "eslint": "^8.57.1", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-react": "^7.26.1", @@ -99,7 +99,7 @@ "timekeeper": "^2.3.1", "typescript": "5.5", "vite": "^5.4.14", - "vitest": "^2.1.1", + "vitest": "^2.1.9", "weak-key": "^1.0.1" } } From 85579d640669e971ef84b63074ae064cfc3e499f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 09:19:43 -0600 Subject: [PATCH 2/4] build(deps): bump cryptography from 43.0.1 to 44.0.1 in /server (#1054) Bumps [cryptography](https://github.com/pyca/cryptography) from 43.0.1 to 44.0.1. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/43.0.1...44.0.1) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Chandra Y --- server/poetry.lock | 91 +++++++++++++++++++++++-------------------- server/pyproject.toml | 2 +- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/server/poetry.lock b/server/poetry.lock index 8e6741a48..59dd383ca 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "amqp" @@ -651,51 +651,55 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "43.0.1" +version = "44.0.1" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, - {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, - {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, - {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, - {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, - {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, - {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, - {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, - {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, - {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, - {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, - {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, - {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +files = [ + {file = "cryptography-44.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd"}, + {file = "cryptography-44.0.1-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf"}, + {file = "cryptography-44.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864"}, + {file = "cryptography-44.0.1-cp37-abi3-win32.whl", hash = "sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a"}, + {file = "cryptography-44.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00"}, + {file = "cryptography-44.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62"}, + {file = "cryptography-44.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b"}, + {file = "cryptography-44.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7"}, + {file = "cryptography-44.0.1-cp39-abi3-win32.whl", hash = "sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9"}, + {file = "cryptography-44.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4"}, + {file = "cryptography-44.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7"}, + {file = "cryptography-44.0.1.tar.gz", hash = "sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.1)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -2137,20 +2141,20 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pyopenssl" -version = "24.2.1" +version = "24.3.0" description = "Python wrapper module around the OpenSSL library" optional = false python-versions = ">=3.7" files = [ - {file = "pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d"}, - {file = "pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95"}, + {file = "pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a"}, + {file = "pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36"}, ] [package.dependencies] -cryptography = ">=41.0.5,<44" +cryptography = ">=41.0.5,<45" [package.extras] -docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"] test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"] [[package]] @@ -2337,6 +2341,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3011,4 +3016,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "2279f1f06e128a87ae4393ce6553b7ca674c71f929c6ed867e940424aee84803" +content-hash = "7ff9fa7dd53c910ed922ae8801244fa08ed56e66a74f79db2d5a177cc6e8dc0d" diff --git a/server/pyproject.toml b/server/pyproject.toml index fb84248b5..bbb8520b1 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -18,7 +18,7 @@ celery = "^5.2.2" django-termsandconditions = "^2.0.11" PyJWT = "^2.6.0" kombu = "^5.2.3" -cryptography = "^43.0.1" +cryptography = "^44.0.1" cached-property = "^1.5.1" ipython = "^8.13.2" pycryptodome = "^3.19.1" From d26520d01214e0a159e3bb648bf5784acc12435e Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Mon, 17 Feb 2025 10:34:56 -0600 Subject: [PATCH 3/4] task/WI-239: Add audit logs for datafiles/projects endpoints (#1047) * add audit logs for datafiles/projects endpoints * unit test fixes * linting fixes * more linting fixes * unit test fixes * pass tracking id to shared workspace mkdir * WI-240: auth, apps, jobs audit logs (#1052) * auth, apps, jobs audit logs * fix test * fix test * fix tests * fix tests --------- Co-authored-by: Sal Tijerina --- server/portal/apps/auth/views.py | 13 +- .../apps/datafiles/handlers/tapis_handlers.py | 12 +- server/portal/apps/datafiles/views.py | 150 +++++++++++++++--- .../portal/apps/datafiles/views_unit_test.py | 12 +- server/portal/apps/projects/unit_test.py | 2 +- server/portal/apps/projects/views.py | 91 ++++++++++- .../portal/apps/projects/views_unit_test.py | 6 +- .../shared_workspace_operations.py | 10 +- server/portal/apps/workspace/api/unit_test.py | 7 +- server/portal/apps/workspace/api/views.py | 123 ++++++++++++-- .../apps/workspace/api/views_unit_test.py | 37 ++--- server/portal/apps/workspace/tasks.py | 54 ------- server/portal/libs/agave/operations.py | 42 +++-- .../portal/libs/agave/operations_unit_test.py | 15 +- server/portal/settings/settings.py | 40 +++-- server/portal/utils/__init__.py | 11 ++ 16 files changed, 455 insertions(+), 170 deletions(-) delete mode 100644 server/portal/apps/workspace/tasks.py diff --git a/server/portal/apps/auth/views.py b/server/portal/apps/auth/views.py index 432fd19a4..fa00f378f 100644 --- a/server/portal/apps/auth/views.py +++ b/server/portal/apps/auth/views.py @@ -18,6 +18,7 @@ ) from portal.apps.search.tasks import index_allocations from portal.apps.users.utils import check_user_groups +from portal.utils import get_client_ip logger = logging.getLogger(__name__) METRICS = logging.getLogger(f'metrics.{__name__}') @@ -138,8 +139,18 @@ def tapis_oauth_callback(request): TapisOAuthToken.objects.update_or_create(user=user, defaults={**token_data}) login(request, user) - METRICS.debug(f"user:{user.username} successful oauth login") launch_setup_checks(user) + METRICS.info( + "Auth", + extra={ + "user": user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "LOGIN", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {}, + }, + ) else: messages.error( request, diff --git a/server/portal/apps/datafiles/handlers/tapis_handlers.py b/server/portal/apps/datafiles/handlers/tapis_handlers.py index 379cd5e9b..000d2fb33 100644 --- a/server/portal/apps/datafiles/handlers/tapis_handlers.py +++ b/server/portal/apps/datafiles/handlers/tapis_handlers.py @@ -15,27 +15,27 @@ } -def tapis_get_handler(client, scheme, system, path, operation, **kwargs): +def tapis_get_handler(client, scheme, system, path, operation, tapis_tracking_id=None, **kwargs): if operation not in allowed_actions[scheme]: raise PermissionDenied op = getattr(operations, operation) - return op(client, system, path, **kwargs) + return op(client, system, path, tapis_tracking_id=tapis_tracking_id, **kwargs) def tapis_post_handler(client, scheme, system, - path, operation, body=None): + path, operation, body=None, tapis_tracking_id=None): if operation not in allowed_actions[scheme]: raise PermissionDenied("") op = getattr(operations, operation) - return op(client, system, path, **body) + return op(client, system, path, tapis_tracking_id=tapis_tracking_id, **body) def tapis_put_handler(client, scheme, system, - path, operation, body=None): + path, operation, body=None, tapis_tracking_id=None): if operation not in allowed_actions[scheme]: raise PermissionDenied op = getattr(operations, operation) - return op(client, system, path, **body) + return op(client, system, path, tapis_tracking_id=tapis_tracking_id, **body) diff --git a/server/portal/apps/datafiles/views.py b/server/portal/apps/datafiles/views.py index 181115272..bedce0174 100644 --- a/server/portal/apps/datafiles/views.py +++ b/server/portal/apps/datafiles/views.py @@ -6,6 +6,7 @@ from requests.exceptions import HTTPError from tapipy.errors import InternalServerError, UnauthorizedError from portal.views.base import BaseApiView +from portal.utils import get_client_ip from portal.libs.agave.utils import service_account from portal.apps.datafiles.handlers.tapis_handlers import (tapis_get_handler, tapis_put_handler, @@ -25,7 +26,7 @@ import dateutil.parser logger = logging.getLogger(__name__) -METRICS = logging.getLogger('metrics.{}'.format(__name__)) +METRICS = logging.getLogger(f"metrics.{__name__}") class SystemListingView(BaseApiView): @@ -97,15 +98,21 @@ def get(self, request, operation=None, scheme=None, system=None, path='/'): {'message': 'This data requires authentication to view.'}, status=403) try: - METRICS.info("user:{} op:{} api:tapis scheme:{} " - "system:{} path:{} filesize:{}".format(request.user.username, - operation, - scheme, - system, - path, - request.GET.get('length'))) + METRICS.info('Data Files', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': operation, + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'api': 'tapis', + 'systemId': system, + 'filePath': path, + 'query': request.GET.dict()} + }) response = tapis_get_handler( - client, scheme, system, path, operation, **request.GET.dict()) + client, scheme, system, path, operation, tapis_tracking_id=f"portals.{request.session.session_key}", **request.GET.dict()) operation in NOTIFY_ACTIONS and \ notify(request.user.username, operation, 'success', {'response': response}) @@ -140,14 +147,22 @@ def put(self, request, operation=None, scheme=None, return HttpResponseForbidden("This data requires authentication to view.") try: - METRICS.info("user:{} op:{} api:tapis scheme:{} " - "system:{} path:{} body:{}".format(request.user.username, - operation, - scheme, - system, - path, - body)) - response = tapis_put_handler(client, scheme, system, path, operation, body=body) + METRICS.info('Data Depot', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': operation, + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'api': 'tapis', + 'scheme': scheme, + 'system': system, + 'path': path, + 'body': body, + } + }) + response = tapis_put_handler(client, scheme, system, path, operation, tapis_tracking_id=f"portals.{request.session.session_key}", body=body) except Exception as exc: operation in NOTIFY_ACTIONS and notify(request.user.username, operation, 'error', {}) raise exc @@ -163,15 +178,22 @@ def post(self, request, operation=None, scheme=None, return HttpResponseForbidden("This data requires authentication to upload.") try: - METRICS.info("user:{} op:{} api:tapis scheme:{} " - "system:{} path:{} filename:{}".format(request.user.username, - operation, - scheme, - system, - path, - body['uploaded_file'].name)) - - response = tapis_post_handler(client, scheme, system, path, operation, body=body) + METRICS.info('Data Files', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': operation, + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'api': 'tapis', + 'scheme': scheme, + 'system': system, + 'path': path, + 'body': request.POST.dict() + }}) + + response = tapis_post_handler(client, scheme, system, path, operation, tapis_tracking_id=f"portals.{request.session.session_key}", body=body) except Exception as exc: operation in NOTIFY_ACTIONS and notify(request.user.username, operation, 'error', {}) raise exc @@ -183,6 +205,19 @@ class GoogleDriveFilesView(BaseApiView): def get(self, request, operation=None, scheme=None, system=None, path='root'): try: + METRICS.info('Data Files', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': operation, + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'api': 'googledrive', + 'systemId': system, + 'filePath': path, + 'query': request.GET.dict()} + }) client = request.user.googledrive_user_token.client except AttributeError: raise ApiException("Login Required", status=400) @@ -230,6 +265,17 @@ def put(self, request, filetype): src_client = get_client(request.user, body['src_api']) dest_client = get_client(request.user, body['dest_api']) + METRICS.info('Data Files', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': 'transfer', + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'body': body + } + }) try: if filetype == 'dir': @@ -275,6 +321,19 @@ def get(self, request, scheme, system, path): """Given a file, returns a link for a file """ try: + METRICS.info('Data Files', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': 'retrieve-postit', + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'api': 'tapis', + 'systemId': system, + 'filePath': path, + 'query': request.GET.dict()} + }) link = Link.objects.get(tapis_uri=f"{system}/{path}") except Link.DoesNotExist: return JsonResponse({"data": None, "expiration": None}) @@ -285,6 +344,19 @@ def delete(self, request, scheme, system, path): """Delete an existing link for a file """ try: + METRICS.info('Data Files', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': 'delete-postit', + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'api': 'tapis', + 'systemId': system, + 'filePath': path, + 'query': request.GET.dict()} + }) link = Link.objects.get(tapis_uri=f"{system}/{path}") except Link.DoesNotExist: raise ApiException("Post-it does not exist") @@ -297,6 +369,19 @@ def post(self, request, scheme, system, path): try: Link.objects.get(tapis_uri=f"{system}/{path}") except Link.DoesNotExist: + METRICS.info('Data Files', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': 'create-postit', + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'api': 'tapis', + 'systemId': system, + 'filePath': path, + 'query': request.GET.dict()} + }) # Link doesn't exist - proceed with creating one postit = self.create_postit(request, scheme, system, path) return JsonResponse({"data": postit['data'], "expiration": postit['expiration']}) @@ -307,6 +392,19 @@ def put(self, request, scheme, system, path): """Replace an existing link for a file """ try: + METRICS.info('Data Files', + extra={ + 'user': request.user.username, + 'sessionId': getattr(request.session, 'session_key', ''), + 'operation': 'replace-postit', + 'agent': request.META.get('HTTP_USER_AGENT'), + 'ip': get_client_ip(request), + 'info': { + 'api': 'tapis', + 'systemId': system, + 'filePath': path, + 'query': request.GET.dict()} + }) link = Link.objects.get(tapis_uri=f"{system}/{path}") self.delete_link(request, link) except Link.DoesNotExist: diff --git a/server/portal/apps/datafiles/views_unit_test.py b/server/portal/apps/datafiles/views_unit_test.py index 85f1bf42a..e7f858b37 100644 --- a/server/portal/apps/datafiles/views_unit_test.py +++ b/server/portal/apps/datafiles/views_unit_test.py @@ -229,9 +229,7 @@ def test_tapis_file_view_get_is_logged_for_metrics(mock_indexer, client, authent } # Ensure metric-related logging is being performed - logging_metric_mock.assert_called_with( - "user:{} op:listing api:tapis scheme:private system:frontera.home.username path:test.txt filesize:1234".format( - authenticated_user.username)) + logging_metric_mock.assert_called() @patch('portal.libs.agave.operations.tapis_indexer') @@ -264,9 +262,7 @@ def test_tapis_file_view_put_is_logged_for_metrics(mock_indexer, client, authent assert response.status_code == 200 # Ensure metric-related logging is being performed - logging_metric_mock.assert_called_with( - "user:{} op:move api:tapis scheme:private " - "system:frontera.home.username path:test.txt body:{}".format(authenticated_user.username, body)) + logging_metric_mock.assert_called() @patch('portal.libs.agave.operations.tapis_indexer') @@ -309,9 +305,7 @@ def test_tapis_file_view_post_is_logged_for_metrics(mock_indexer, client, authen assert response.json() == {"data": tapis_file_mock} # Ensure metric-related logging is being performed - logging_metric_mock.assert_called_with( - "user:{} op:upload api:tapis scheme:private " - "system:frontera.home.username path:/ filename:text_file.txt".format(authenticated_user.username)) + logging_metric_mock.assert_called() @patch('portal.libs.agave.operations.tapis_indexer') diff --git a/server/portal/apps/projects/unit_test.py b/server/portal/apps/projects/unit_test.py index 083ad4aaa..647dedac1 100644 --- a/server/portal/apps/projects/unit_test.py +++ b/server/portal/apps/projects/unit_test.py @@ -146,7 +146,7 @@ def create_shared_workspace( mock_create_workspace_dir.assert_called() mock_service_account.assert_called() mock_service_account().files.mkdir.assert_called_with( - systemId="projects.system.name", path=f"test.project-{workspace_num}" + systemId="projects.system.name", path=f"test.project-{workspace_num}", headers={'X-Tapis-Tracking-ID': ''} ) # Set Workspace ACLS # Authenticated_user is whoever the mock_owner or creator of the project is diff --git a/server/portal/apps/projects/views.py b/server/portal/apps/projects/views.py index 63d3f4617..d41a375fd 100644 --- a/server/portal/apps/projects/views.py +++ b/server/portal/apps/projects/views.py @@ -9,6 +9,7 @@ from django.conf import settings from django.http import JsonResponse from django.utils.decorators import method_decorator +from portal.utils import get_client_ip from portal.utils.decorators import agave_jwt_login from portal.exceptions.api import ApiException from portal.views.base import BaseApiView @@ -22,6 +23,7 @@ from elasticsearch_dsl import Q LOGGER = logging.getLogger(__name__) +METRICS = logging.getLogger(f"metrics.{__name__}") @method_decorator(agave_jwt_login, name='dispatch') @@ -70,6 +72,18 @@ def get(self, request): offset = int(request.GET.get('offset', 0)) limit = int(request.GET.get('limit', 100)) + METRICS.info( + "Projects", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "projects.listing", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {}, + }, + ) + listing = [] if query_string: @@ -108,7 +122,19 @@ def post(self, request): # pylint: disable=no-self-use title = data['title'] client = request.user.tapis_oauth.client - system_id = create_shared_workspace(client, title, request.user.username) + system_id = create_shared_workspace(client, title, request.user.username, tapis_tracking_id=f"portals.{request.session.session_key}") + + METRICS.info( + "Projects", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "projects.create", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"body": data, "id": system_id}, + }, + ) return JsonResponse( { @@ -141,6 +167,18 @@ def get(self, request, project_id=None, system_id=None): if system_id is not None: project_id = system_id.split(f"{settings.PORTAL_PROJECTS_SYSTEM_PREFIX}.")[1] + METRICS.info( + "Projects", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "projects.detail", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"project_id": project_id}, + }, + ) + prj = get_project(request.user.tapis_oauth.client, project_id) return JsonResponse( @@ -184,6 +222,18 @@ def patch( """ data = json.loads(request.body) + METRICS.info( + "Projects", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "projects.patch", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"body": data}, + }, + ) + client = request.user.tapis_oauth.client workspace_def = update_project(client, project_id, data['title'], data['description']) return JsonResponse( @@ -219,6 +269,19 @@ def patch(self, request, project_id): 403, request.POST.dict() ) + + METRICS.info( + "Projects", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "projects.patchMembers", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"body": data}, + }, + ) + return operation(request, project_id, **data) def transfer_ownership(self, request, project_id, **data): @@ -310,6 +373,19 @@ def change_system_role(self, request, project_Id, **data): def get_project_role(request, project_id, username): role = None client = request.user.tapis_oauth.client + + METRICS.info( + "Projects", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "projects.get_project_role", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"project_id": project_id, "username": username}, + }, + ) + role = get_workspace_role(client, project_id, username) return JsonResponse({'username': username, 'role': role}) @@ -318,6 +394,19 @@ def get_project_role(request, project_id, username): @login_required def get_system_role(request, project_id, username): client = request.user.tapis_oauth.client + + METRICS.info( + "Projects", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "projects.get_system_role", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"project_id": project_id, "username": username}, + }, + ) + role = get_workspace_role(client, project_id, username) return JsonResponse({'username': username, 'role': role}) diff --git a/server/portal/apps/projects/views_unit_test.py b/server/portal/apps/projects/views_unit_test.py index c47b38e41..ea28fe36e 100644 --- a/server/portal/apps/projects/views_unit_test.py +++ b/server/portal/apps/projects/views_unit_test.py @@ -198,7 +198,8 @@ def test_projects_post( # 2. service account client sets client.files.setFacl # 3. standard client creates workspace client.systems.createSystem mock_service_account().files.mkdir.assert_called_with( - systemId="projects.system.name", path="test.project-2" + systemId="projects.system.name", path="test.project-2", + headers={"X-Tapis-Tracking-ID": f"portals.{client.session.session_key}"} ) mock_service_account().files.setFacl.assert_called_with( systemId="projects.system.name", @@ -237,7 +238,8 @@ def test_projects_post_setfacl_job( # 2. service account client sets client.files.setFacl # 3. standard client creates workspace client.systems.createSystem mock_service_account().files.mkdir.assert_called_with( - systemId="projects.system.name", path="test.project-2" + systemId="projects.system.name", path="test.project-2", + headers={"X-Tapis-Tracking-ID": f"portals.{client.session.session_key}"} ) mock_service_account().files.setFacl.assert_not_called() mock_service_account().jobs.submitJob.assert_called_with( diff --git a/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py b/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py index 3bdd0c62c..79872732c 100644 --- a/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py +++ b/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py @@ -110,11 +110,13 @@ def submit_workspace_acls_job( return res -def create_workspace_dir(workspace_id: str) -> str: +def create_workspace_dir(workspace_id: str, **kwargs) -> str: client = service_account() system_id = settings.PORTAL_PROJECTS_ROOT_SYSTEM_NAME path = f"{workspace_id}" - client.files.mkdir(systemId=system_id, path=path) + client.files.mkdir(systemId=system_id, + path=path, + headers={"X-Tapis-Tracking-ID": kwargs.get("tapis_tracking_id", "")}) return path @@ -161,7 +163,7 @@ def increment_workspace_count(force=None) -> int: ########################################## -def create_shared_workspace(client: Tapis, title: str, owner: str): +def create_shared_workspace(client: Tapis, title: str, owner: str, **kwargs): """ Create a workspace system owned by user whose client is passed. """ @@ -170,7 +172,7 @@ def create_shared_workspace(client: Tapis, title: str, owner: str): workspace_id = f"{settings.PORTAL_PROJECTS_ID_PREFIX}-{workspace_number}" # Service client creates directory and gives owner write permissions - create_workspace_dir(workspace_id) + create_workspace_dir(workspace_id, **kwargs) set_workspace_acls(service_client, settings.PORTAL_PROJECTS_ROOT_SYSTEM_NAME, workspace_id, diff --git a/server/portal/apps/workspace/api/unit_test.py b/server/portal/apps/workspace/api/unit_test.py index 1ba9f0fd2..43d8b0ff9 100644 --- a/server/portal/apps/workspace/api/unit_test.py +++ b/server/portal/apps/workspace/api/unit_test.py @@ -21,7 +21,12 @@ def test_job_history_get(self): job_uuid = "032142c3-ac6a-42cb-841e-fbc26a2d951c-007" self.mock_tapis_client.jobs.getJobHistory.return_value = "mock_response" response = self.client.get("/api/workspace/jobs/{}/history".format(job_uuid)) - self.mock_tapis_client.jobs.getJobHistory.assert_called_with(jobUuid=job_uuid) + self.mock_tapis_client.jobs.getJobHistory.assert_called_with( + jobUuid=job_uuid, + headers={ + "X-Tapis-Tracking-ID": f"portals.{self.client.session.session_key}" + }, + ) data = json.loads(response.content) self.assertEqual(data, {"status": 200, "response": "mock_response"}) diff --git a/server/portal/apps/workspace/api/views.py b/server/portal/apps/workspace/api/views.py index a55fde210..2205c0856 100644 --- a/server/portal/apps/workspace/api/views.py +++ b/server/portal/apps/workspace/api/views.py @@ -11,6 +11,7 @@ from django.urls import reverse from django.db.models.functions import Coalesce from django.core.exceptions import PermissionDenied +from tapipy.tapis import TapisResult from tapipy.errors import InternalServerError, UnauthorizedError from portal.views.base import BaseApiView from portal.exceptions.api import ApiException @@ -27,6 +28,8 @@ check_job_for_timeout, test_system_credentials, ) +from portal.utils import get_client_ip + logger = logging.getLogger(__name__) METRICS = logging.getLogger('metrics.{}'.format(__name__)) @@ -90,8 +93,18 @@ def get(self, request, *args, **kwargs): tapis = request.user.tapis_oauth.client app_id = request.GET.get('appId') if app_id: + METRICS.info( + "Apps", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "getApp", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"query": request.GET.dict()}, + }, + ) app_version = request.GET.get('appVersion') - METRICS.info("user:{} is requesting app id:{} version:{}".format(request.user.username, app_id, app_version)) data = _get_app(app_id, app_version, request.user) # Check if default storage system needs keys pushed @@ -106,7 +119,17 @@ def get(self, request, *args, **kwargs): data['systemNeedsKeys'] = not success data['pushKeysSystem'] = system_def else: - METRICS.info("user:{} is requesting all apps".format(request.user.username)) + METRICS.info( + "Apps", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "getApps", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"query": request.GET.dict()}, + }, + ) data = {'appListing': tapis.apps.getApps()} return JsonResponse( @@ -143,6 +166,18 @@ def get(self, request, operation=None): if operation not in allowed_actions: raise PermissionDenied + METRICS.info( + "Jobs", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": operation, + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"query": request.GET.dict()}, + }, + ) + op = getattr(self, operation) data = op(tapis, request) @@ -162,7 +197,7 @@ def get(self, request, operation=None): def select(self, client, request): job_uuid = request.GET.get('job_uuid') - data = client.jobs.getJob(jobUuid=job_uuid) + data = client.jobs.getJob(jobUuid=job_uuid, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"}) return data @@ -176,7 +211,7 @@ def listing(self, client, request): skip=offset, orderBy='lastUpdated(desc),name(asc)', _tapis_query_parameters={'tags.contains': f'portalName: {portal_name}'}, - select='allAttributes' + select='allAttributes', headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"} ) return data @@ -209,15 +244,25 @@ def search(self, client, request): request_body={ "search": sql_queries }, - select="allAttributes" + select="allAttributes", headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"} ) return data def delete(self, request, *args, **kwargs): + METRICS.info( + "Jobs", + extra={ + "user": request.user.username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "delete", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": {"query": request.GET.dict()}, + }, + ) tapis = request.user.tapis_oauth.client job_uuid = request.GET.get('job_uuid') - METRICS.info("user:{} is deleting job uuid:{}".format(request.user.username, job_uuid)) - data = tapis.jobs.hideJob(jobUuid=job_uuid) + data = tapis.jobs.hideJob(jobUuid=job_uuid, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"}) return JsonResponse( { 'status': 200, @@ -237,11 +282,47 @@ def post(self, request, *args, **kwargs): if job_uuid and job_action: if job_action == 'resubmit': METRICS.info("user:{} is resubmitting job uuid:{}".format(username, job_uuid)) - data = tapis.jobs.resubmitJob(jobUuid=job_uuid) + data = tapis.jobs.resubmitJob(jobUuid=job_uuid, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"}) + if isinstance(data, TapisResult): + metrics_info = { + "body": body, + } + response_uuid = data.get("uuid", None) + if response_uuid: + metrics_info["response_uuid"] = response_uuid + METRICS.info( + "Jobs", + extra={ + "user": username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "resubmitJob", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": metrics_info, + }, + ) elif job_action == 'cancel': METRICS.info("user:{} is canceling/stopping job uuid:{}".format(username, job_uuid)) - data = tapis.jobs.cancelJob(jobUuid=job_uuid) + data = tapis.jobs.cancelJob(jobUuid=job_uuid, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"}) + if isinstance(data, TapisResult): + metrics_info = { + "body": body, + } + response_uuid = data.get("uuid", None) + if response_uuid: + metrics_info["response_uuid"] = response_uuid + METRICS.info( + "Jobs", + extra={ + "user": username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "cancelJob", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": metrics_info, + }, + ) else: raise ApiException("user:{} is trying to run an unsupported job action: {} for job uuid: {}".format( username, @@ -356,7 +437,27 @@ def post(self, request, *args, **kwargs): ] logger.info("user:{} is submitting job:{}".format(username, job_post)) - response = tapis.jobs.submitJob(**job_post) + response = tapis.jobs.submitJob(**job_post, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"}) + + if isinstance(response, TapisResult): + metrics_info = { + "body": body, + } + response_uuid = response.get("uuid", None) + if response_uuid: + metrics_info["response_uuid"] = response_uuid + METRICS.info( + "Jobs", + extra={ + "user": username, + "sessionId": getattr(request.session, "session_key", ""), + "operation": "submitJob", + "agent": request.META.get("HTTP_USER_AGENT"), + "ip": get_client_ip(request), + "info": metrics_info, + }, + ) + return JsonResponse( { 'status': 200, @@ -401,7 +502,7 @@ def post(self, request, *args, **kwargs): class JobHistoryView(BaseApiView): def get(self, request, job_uuid): tapis = request.user.tapis_oauth.client - data = tapis.jobs.getJobHistory(jobUuid=job_uuid) + data = tapis.jobs.getJobHistory(jobUuid=job_uuid, headers={"X-Tapis-Tracking-ID": f"portals.{request.session.session_key}"}) return JsonResponse( { 'status': 200, diff --git a/server/portal/apps/workspace/api/views_unit_test.py b/server/portal/apps/workspace/api/views_unit_test.py index e0923fd78..08fbc0da2 100644 --- a/server/portal/apps/workspace/api/views_unit_test.py +++ b/server/portal/apps/workspace/api/views_unit_test.py @@ -1,5 +1,5 @@ from django.conf import settings -from portal.apps.workspace.api.views import JobsView, AppsTrayView, AppsView +from portal.apps.workspace.api.views import AppsTrayView from portal.apps.workspace.models import AppTrayCategory from portal.apps.workspace.models import JobSubmission import json @@ -198,32 +198,30 @@ def test_job_post_is_logged_for_metrics( ) -def request_jobs_util(rf, authenticated_user, query_params={}): - # Unit test helper function - view = JobsView() - request = rf.get("/api/workspace/jobs/", query_params) - request.user = authenticated_user - operation = "listing" - response = view.get(request, operation) - return json.loads(response.content)["response"] +def request_jobs_util(client, authenticated_user, query_params={}): + response = client.get( + "/api/workspace/jobs/listing", + query_params, + ) + return response.json()["response"] -def test_get_no_tapis_jobs(rf, authenticated_user, mock_tapis_client): +def test_get_no_tapis_jobs(client, authenticated_user, mock_tapis_client): mock_tapis_client.jobs.getJobSearchList.return_value = [] - jobs = request_jobs_util(rf, authenticated_user) + jobs = request_jobs_util(client, authenticated_user) assert len(jobs) == 0 -def test_get_no_portal_jobs(rf, authenticated_user, mock_tapis_client): +def test_get_no_portal_jobs(client, authenticated_user, mock_tapis_client): JobSubmission.objects.create(user=authenticated_user, jobId="9876") mock_tapis_client.jobs.getJobSearchList.return_value = [] - jobs = request_jobs_util(rf, authenticated_user) + jobs = request_jobs_util(client, authenticated_user) assert len(jobs) == 0 -def test_get_jobs_bad_offset(rf, authenticated_user, mock_tapis_client): +def test_get_jobs_bad_offset(client, authenticated_user, mock_tapis_client): mock_tapis_client.jobs.getJobSearchList.return_value = [] - jobs = request_jobs_util(rf, authenticated_user, query_params={"offset": 100}) + jobs = request_jobs_util(client, authenticated_user, query_params={"offset": 100}) assert len(jobs) == 0 @@ -280,7 +278,7 @@ def test_get_app_dynamic_exec_sys( django_db_blocker, mock_tapis_client, authenticated_user, - rf, + client, tapis_get_systems_list, ): # Load fixtures @@ -299,13 +297,10 @@ def test_get_app_dynamic_exec_sys( else: mock_tapis_client.systems.getSystem.return_value = tapis_get_systems_list[1] # invoke and assert - apps_view = AppsView() query_params = {"appId": "hello-world", "appVersion": "0.0.1"} - request = rf.get("/api/workspace/apps/", query_params) - request.user = authenticated_user - response = apps_view.get(request) + response = client.get("/api/workspace/apps/", query_params) assert response.status_code == 200 - response_json = json.loads(response.content)["response"] + response_json = response.json()["response"] assert response_json["definition"]["id"] == "hello-world" assert response_json["definition"]["version"] == "0.0.1" if dynamic_exec_system: diff --git a/server/portal/apps/workspace/tasks.py b/server/portal/apps/workspace/tasks.py deleted file mode 100644 index 796397725..000000000 --- a/server/portal/apps/workspace/tasks.py +++ /dev/null @@ -1,54 +0,0 @@ -from django.contrib.auth import get_user_model -from requests import ConnectionError, HTTPError -import logging - -logger = logging.getLogger(__name__) - - -class JobSubmitError(Exception): - - def __init__(self, *args, **kwargs): - self.status = kwargs.pop('status', 'error') - self.status_code = kwargs.pop('status_code', 500) - self.message = kwargs.pop('message', None) - - def json(self): - return { - 'status': getattr(self, 'status', 'error'), - 'message': getattr(self, 'message', None) - } - - -def submit_job(request, username, job_post): - logger.info('Submitting job for user=%s: %s' % (username, job_post)) - - try: - user = get_user_model().objects.get(username=username) - agave = user.tapis_oauth.client - response = agave.jobs.submit(body=job_post) - logger.debug('Job Submission Response: {}'.format(response)) - - return response - - except ConnectionError as e: - logger.error('ConnectionError while submitting job: %s' % e, - extra={'job': job_post}) - raise JobSubmitError(status='error', - status_code=500, - message='We were unable to submit your job at this time due ' - 'to a Job Service Interruption. Please try again later.') - - except HTTPError as e: - logger.error('HTTPError while submitting job: %s' % e, - extra={'job': job_post}) - if e.response.status_code >= 500: - raise JobSubmitError( - status='error', - status_code=e.response.status_code, - message='We were unable to submit your job at this time due ' - 'to a Job Service Interruption. Please try again later.') - - err_resp = e.response.json() - err_resp['status_code'] = e.response.status_code - logger.warning(err_resp) - raise JobSubmitError(**err_resp) diff --git a/server/portal/libs/agave/operations.py b/server/portal/libs/agave/operations.py index 83264a1a8..c45c2a080 100644 --- a/server/portal/libs/agave/operations.py +++ b/server/portal/libs/agave/operations.py @@ -40,7 +40,8 @@ def listing(client, system, path, offset=0, limit=100, *args, **kwargs): raw_listing = client.files.listFiles(systemId=system, path=path, offset=int(offset), - limit=int(limit)) + limit=int(limit), + headers={"X-Tapis-Tracking-ID": kwargs.get("tapis_tracking_id", "")}) try: # Convert file objects to dicts for serialization. @@ -176,7 +177,7 @@ def download(client, system, path, max_uses=3, lifetime=600, **kwargs): return redeemUrl -def mkdir(client, system, path, dir_name): +def mkdir(client, system, path, dir_name, **kwargs): """Create a new directory. Params @@ -207,7 +208,7 @@ def mkdir(client, system, path, dir_name): return {"result": "OK"} -def move(client, src_system, src_path, dest_system, dest_path, file_name=None): +def move(client, src_system, src_path, dest_system, dest_path, file_name=None, **kwargs): """Move a current file to the given destination. Params @@ -251,7 +252,8 @@ def move(client, src_system, src_path, dest_system, dest_path, file_name=None): move_result = client.files.moveCopy(systemId=src_system, path=src_path, operation="MOVE", - newPath=dest_path_full) + newPath=dest_path_full, + headers={"X-Tapis-Tracking-ID": kwargs.get("tapis_tracking_id", "")}) if os.path.dirname(src_path) != dest_path or src_path != dest_path: tapis_indexer.apply_async(kwargs={'access_token': client.access_token.access_token, @@ -318,7 +320,8 @@ def copy(client, src_system, src_path, dest_system, dest_path, file_name=None, copy_result = client.files.moveCopy(systemId=src_system, path=src_path, operation="COPY", - newPath=dest_path_full) + newPath=dest_path_full, + headers={"X-Tapis-Tracking-ID": kwargs.get("tapis_tracking_id", "")}) else: src_url = f'tapis://{src_system}/{src_path}' @@ -327,7 +330,7 @@ def copy(client, src_system, src_path, dest_system, dest_path, file_name=None, copy_response = client.files.createTransferTask(elements=[{ 'sourceURI': src_url, 'destinationURI': dest_url - }]) + }], headers={"X-Tapis-Tracking-ID": kwargs.get("tapis_tracking_id", "")}) copy_result = { 'uuid': copy_response.uuid, @@ -364,12 +367,13 @@ def makepublic(client, src_system, src_path, dest_path='/', *args, **kwargs): *args, **kwargs) -def delete(client, system, path): +def delete(client, system, path, *args, **kwargs): return client.files.delete(systemId=system, - path=path) + path=path, + headers={"X-Tapis-Tracking-ID": kwargs.get("tapis_tracking_id", "")}) -def rename(client, system, path, new_name): +def rename(client, system, path, new_name, *args, **kwargs): """Renames a file. This is performed under the hood by moving the file to the same parent folder but with a new name. @@ -390,10 +394,10 @@ def rename(client, system, path, new_name): """ new_path = os.path.dirname(path) return move(client, src_system=system, src_path=path, - dest_system=system, dest_path=new_path, file_name=new_name) + dest_system=system, dest_path=new_path, file_name=new_name, **kwargs) -def trash(client, system, path, homeDir): +def trash(client, system, path, homeDir, *args, **kwargs): """Move a file to the .Trash folder. Params @@ -423,12 +427,12 @@ def trash(client, system, path, homeDir): mkdir(client, system, homeDir, settings.TAPIS_DEFAULT_TRASH_NAME) resp = move(client, system, path, system, - f'{homeDir}/{settings.TAPIS_DEFAULT_TRASH_NAME}', file_name) + f'{homeDir}/{settings.TAPIS_DEFAULT_TRASH_NAME}', file_name, **kwargs) return resp -def upload(client, system, path, uploaded_file): +def upload(client, system, path, uploaded_file, *args, **kwargs): """Upload a file. Params ------ @@ -449,7 +453,10 @@ def upload(client, system, path, uploaded_file): uploaded_file.name = increment_file_name(listing=file_listing, file_name=uploaded_file.name) dest_path = os.path.join(path.strip('/'), uploaded_file.name) - response_json = client.files.insert(systemId=system, path=dest_path, file=uploaded_file) + response_json = client.files.insert(systemId=system, + path=dest_path, + file=uploaded_file, + headers={"X-Tapis-Tracking-ID": kwargs.get("tapis_tracking_id", "")}) tapis_indexer.apply_async(kwargs={'access_token': client.access_token.access_token, 'systemId': system, 'filePath': path, @@ -482,7 +489,10 @@ def preview(client, system, path, max_uses=3, lifetime=600, **kwargs): file_name = path.strip('/').split('/')[-1] file_ext = os.path.splitext(file_name)[1].lower() - postit = client.files.createPostIt(systemId=system, path=path, allowedUses=max_uses, validSeconds=lifetime) + postit = client.files.createPostIt(systemId=system, + path=path, allowedUses=max_uses, + validSeconds=lifetime, + headers={"X-Tapis-Tracking-ID": kwargs.get("tapis_tracking_id", "")}) url = postit.redeemUrl txt = None @@ -521,7 +531,7 @@ def preview(client, system, path, max_uses=3, lifetime=600, **kwargs): return {'href': url, 'fileType': file_type, 'content': txt, 'error': error} -def download_bytes(client, system, path): +def download_bytes(client, system, path, *args, **kwargs): """Returns a BytesIO object representing the file. Params diff --git a/server/portal/libs/agave/operations_unit_test.py b/server/portal/libs/agave/operations_unit_test.py index 81ddfaa85..b4049fd14 100644 --- a/server/portal/libs/agave/operations_unit_test.py +++ b/server/portal/libs/agave/operations_unit_test.py @@ -28,7 +28,8 @@ def test_listing(self, mock_indexer): client.files.listFiles.assert_called_with(systemId='test.system', path='/path/to/file', offset=1, - limit=100) + limit=100, + headers={'X-Tapis-Tracking-ID': ''}) mock_response_listing = [{'system': 'test.system', 'type': 'file', @@ -112,7 +113,11 @@ def test_move(self, mock_indexer): move(client, 'test.system', '/path/to/src', 'test.system', '/path/to/dest') - client.files.moveCopy.assert_called_with(systemId='test.system', path='/path/to/src', operation='MOVE', newPath='path/to/dest/src') + client.files.moveCopy.assert_called_with(systemId='test.system', + path='/path/to/src', + operation='MOVE', + newPath='path/to/dest/src', + headers={'X-Tapis-Tracking-ID': ''}) self.assertEqual(mock_indexer.apply_async.call_count, 3) @@ -128,7 +133,11 @@ def test_copy(self, mock_indexer): copy(client, 'test.system', '/path/to/src', 'test.system', '/path/to/dest') - client.files.moveCopy.assert_called_with(systemId='test.system', path='/path/to/src', operation='COPY', newPath='path/to/dest/src') + client.files.moveCopy.assert_called_with(systemId='test.system', + path='/path/to/src', + operation='COPY', + newPath='path/to/dest/src', + headers={'X-Tapis-Tracking-ID': ''}) self.assertEqual(mock_indexer.apply_async.call_count, 2) diff --git a/server/portal/settings/settings.py b/server/portal/settings/settings.py index 1321974d1..09d7ef2d2 100644 --- a/server/portal/settings/settings.py +++ b/server/portal/settings/settings.py @@ -11,6 +11,7 @@ """ import os +import uuid import logging from kombu import Exchange, Queue from portal.settings import settings_secret @@ -289,9 +290,25 @@ SETTINGS: LOGGING """ + +def portal_filter(record): + """Log filter that adds portal-specific vars to each entry""" + + record.logGuid = uuid.uuid4().hex + record.portal = PORTAL_NAMESPACE + record.tenant = TAPIS_TENANT_BASEURL + return True + + LOGGING = { 'version': 1, 'disable_existing_loggers': False, + "filters": { + "portalFilter": { + "()": "django.utils.log.CallbackFilter", + "callback": portal_filter, + }, + }, 'formatters': { 'default': { 'format': '[DJANGO] %(levelname)s %(asctime)s UTC %(module)s ' @@ -302,8 +319,9 @@ '%(name)s.%(funcName)s:%(lineno)s: %(message)s' }, 'metrics': { - 'format': '[METRICS] %(levelname)s %(asctime)s UTC %(module)s ' - '%(name)s.%(funcName)s:%(lineno)s: %(message)s' + 'format': '[METRICS] %(levelname)s %(module)s %(name)s.%(funcName)s:%(lineno)s:' + ' %(message)s user=%(user)s ip=%(ip)s agent=%(agent)s sessionId=%(sessionId)s op=%(operation)s' + ' info=%(info)s timestamp=%(asctime)s trackingId=portals.%(sessionId)s guid=%(logGuid)s portal=%(portal)s tenant=%(tenant)s' }, }, 'handlers': { @@ -320,18 +338,11 @@ 'backupCount': 5, 'formatter': 'default', }, - 'metrics_console': { - 'level': 'DEBUG', + 'metrics': { + 'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'metrics', - }, - 'metrics_file': { - 'level': 'DEBUG', - 'class': 'logging.handlers.RotatingFileHandler', - 'filename': '/var/log/portal/metrics.log', - 'maxBytes': 1024*1024*5, # 5 MB - 'backupCount': 5, - 'formatter': 'metrics', + 'filters': ['portalFilter'] }, }, 'loggers': { @@ -345,8 +356,9 @@ 'level': 'DEBUG', }, 'metrics': { - 'handlers': ['metrics_console', 'metrics_file'], - 'level': 'DEBUG', + 'handlers': ['metrics'], + 'filters': ['portalFilter'], + 'level': 'INFO', }, 'paramiko': { 'handlers': ['console'], diff --git a/server/portal/utils/__init__.py b/server/portal/utils/__init__.py index e69de29bb..4110a1de2 100644 --- a/server/portal/utils/__init__.py +++ b/server/portal/utils/__init__.py @@ -0,0 +1,11 @@ +"""Utils for use across multiple apps""" + + +def get_client_ip(request): + """Extract an IP address from a Django request object.""" + x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") + if x_forwarded_for: + ip = x_forwarded_for.split(",")[-1].strip() + else: + ip = request.META.get("REMOTE_ADDR") + return ip From 96eb02e1141c4817312ecb9e57ccdc83d5d96bb6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:13:49 -0600 Subject: [PATCH 4/4] build(deps): bump dompurify from 2.5.7 to 3.2.4 in /client (#1058) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.5.7 to 3.2.4. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/2.5.7...3.2.4) --- updated-dependencies: - dependency-name: dompurify dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Chandra Y --- client/package-lock.json | 19 +++++++++++++++---- client/package.json | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 7090aefca..b65eea9c3 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -16,7 +16,7 @@ "axios": "^1.7.7", "bowser": "^2.9.0", "cross-fetch": "^3.1.4", - "dompurify": "^2.5.4", + "dompurify": "^3.2.4", "formik": "^2.2.9", "html-react-parser": "^1.4.8", "js-cookie": "^2.2.1", @@ -5056,6 +5056,13 @@ "@types/jest": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/yargs": { "version": "16.0.9", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", @@ -7212,9 +7219,13 @@ } }, "node_modules/dompurify": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz", - "integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==" + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", + "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } }, "node_modules/domutils": { "version": "2.8.0", diff --git a/client/package.json b/client/package.json index ae12d6230..6b23f0e1e 100644 --- a/client/package.json +++ b/client/package.json @@ -12,7 +12,7 @@ "axios": "^1.7.7", "bowser": "^2.9.0", "cross-fetch": "^3.1.4", - "dompurify": "^2.5.4", + "dompurify": "^3.2.4", "formik": "^2.2.9", "html-react-parser": "^1.4.8", "js-cookie": "^2.2.1",