From 28237c2ad81aeddaea58e692f55301f421265393 Mon Sep 17 00:00:00 2001 From: Rose Toomey Date: Sat, 14 Dec 2024 11:33:32 +0100 Subject: [PATCH 1/3] Make time dropdowns user configurable via an environmental config file path supplied as a build arg to Docker --- .env.production.example | 2 +- .gitignore | 6 + Dockerfile | 15 +- README.md | 23 + craco.config.js | 46 +- docker-compose.yml | 6 +- package-lock.json | 845 ++++++++++++++----------------- package.json | 4 +- src/config/accessConfig.ts | 10 + src/config/config.default.json | 12 + src/config/config.empty.json | 1 + src/globals.d.ts | 1 + src/pages/groups/AddRoles.tsx | 32 +- src/pages/groups/AddUsers.tsx | 32 +- src/pages/groups/BulkRenewal.tsx | 25 +- src/pages/requests/Create.tsx | 27 +- src/pages/requests/Read.tsx | 23 +- src/pages/roles/AddGroups.tsx | 25 +- src/pages/roles/BulkRenewal.tsx | 26 +- tsconfig.json | 13 +- 20 files changed, 558 insertions(+), 616 deletions(-) create mode 100644 src/config/accessConfig.ts create mode 100644 src/config/config.default.json create mode 100644 src/config/config.empty.json create mode 100644 src/globals.d.ts diff --git a/.env.production.example b/.env.production.example index 8fb6c56..42dc807 100644 --- a/.env.production.example +++ b/.env.production.example @@ -12,4 +12,4 @@ REACT_SENTRY_DSN=https://@sentry.io/ CLOUDFLARE_TEAM_DOMAIN= CLOUDFLARE_APPLICATION_AUDIENCE= SECRET_KEY= -OIDC_CLIENT_SECRETS= \ No newline at end of file +OIDC_CLIENT_SECRETS= diff --git a/.gitignore b/.gitignore index 6205b89..a5aa6d7 100644 --- a/.gitignore +++ b/.gitignore @@ -497,3 +497,9 @@ $RECYCLE.BIN/ *.lnk # End of https://www.toptal.com/developers/gitignore/api/macos,windows,visualstudiocode,jetbrains+all,node,python,flask + +config.production.json +config.staging.json +config.development.json +config.test.json +config.override.json diff --git a/Dockerfile b/Dockerfile index 5173c28..272a99f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,14 +6,17 @@ ARG PUSH_SENTRY_RELEASE="false" FROM node:22-alpine AS build-step ARG SENTRY_RELEASE="" WORKDIR /app -ENV PATH /app/node_modules/.bin:$PATH +ENV PATH=/app/node_modules/.bin:$PATH COPY craco.config.js package.json package-lock.json tsconfig.json tsconfig.paths.json .env.production* ./ COPY ./src ./src COPY ./public ./public +ARG ACCESS_FILE_CONFIG_PATH="src/config/config.empty.json" +COPY $ACCESS_FILE_CONFIG_PATH /app/config.override.json + RUN npm install RUN touch .env.production -ENV REACT_APP_SENTRY_RELEASE $SENTRY_RELEASE -ENV REACT_APP_API_SERVER_URL "" +ENV REACT_APP_SENTRY_RELEASE=$SENTRY_RELEASE +ENV REACT_APP_API_SERVER_URL="" RUN npm run build # Optional build step #2: upload the source maps by pushing a release to sentry @@ -48,9 +51,9 @@ COPY --from=sentry /app/sentry ./sentry # Choose whether to include the sentry release push build step or not FROM ${PUSH_SENTRY_RELEASE} -ENV FLASK_ENV production -ENV FLASK_APP api.app:create_app -ENV SENTRY_RELEASE $SENTRY_RELEASE +ENV FLASK_ENV=production +ENV FLASK_APP=api.app:create_app +ENV SENTRY_RELEASE=$SENTRY_RELEASE EXPOSE 3000 diff --git a/README.md b/README.md index 1042d22..dd17364 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,29 @@ If you are using Cloudflare Access, ensure that you configure `CLOUDFLARE_TEAM_D Else, if you are using a generic OIDC identity provider (such as Okta), then you should configure `SECRET_KEY` and `OIDC_CLIENT_SECRETS`. `CLOUDFLARE_TEAM_DOMAIN` and `CLOUDFLARE_APPLICATION_AUDIENCE` do not need to be set and can be removed from your env file. Make sure to also mount your `client-secrets.json` file to the container if you don't have it inline. +### Access application configuration overrides + +The default config for the application is at `src/config/config.default.json`. + +If you want to override those values, create your own config file containing JSON that overrides values in the default config. + +- `ACCESS_TIME_LABELS`: _Optional._ Specifies the time access labels to use for dropdowns on the front end. Contains a JSON object of the format `{"NUM_SECONDS": "LABEL"}`. **Example:** `{"86400": "1 day", "604800": "1 week", "2592000": "1 month"}`. +- `DEFAULT_ACCESS_TIME`: _Optional._ Specifies the default time access label to use for dropdowns on the front end. Contains a string with a number of seconds corresponding to a key in the access time labels, e.g. `"86400"`. + +To use your custom config file, edit `docker-compose.yml` to add a build arg `ACCESS_FILE_CONFIG_PATH` with a local path to your config override file: + +```yaml +services: + discord-access: + build: + context: . + dockerfile: Dockerfile + args: + ACCESS_FILE_CONFIG_PATH: 'path/to/config.production.json' +``` + +If `ACCESS_FILE_CONFIG_PATH` is not set as a build arg when building the Docker image, then the default config will be used. + #### Database Setup After `docker compose up --build`, you can run the following commands to setup the database: diff --git a/craco.config.js b/craco.config.js index 5fd85c1..e3161c5 100644 --- a/craco.config.js +++ b/craco.config.js @@ -1,4 +1,44 @@ const CracoAlias = require('react-app-alias'); +const fs = require('fs'); +const path = require('path'); +const webpack = require('webpack'); + +function load_access_config() { + // Load the default config + const defaultConfigPath = path.resolve(__dirname, 'src/config/config.default.json'); + const accessConfig = JSON.parse(fs.readFileSync(defaultConfigPath, 'utf8')); + + // Check for config.override.json + const overrideConfigPath = path.resolve(__dirname, 'config.override.json'); + if (fs.existsSync(overrideConfigPath)) { + const overrideConfig = JSON.parse(fs.readFileSync(overrideConfigPath, 'utf8')); + Object.assign(accessConfig, overrideConfig); + } else { + // Check for ACCESS_FILE_CONFIG_PATH environment variable + const envConfigPath = process.env.ACCESS_FILE_CONFIG_PATH; + if (envConfigPath && fs.existsSync(envConfigPath)) { + const envConfig = JSON.parse(fs.readFileSync(envConfigPath, 'utf8')); + Object.assign(accessConfig, envConfig); + } + } + + // Sanity check for ACCESS_TIME_LABELS + if (accessConfig.ACCESS_TIME_LABELS && typeof accessConfig.ACCESS_TIME_LABELS !== 'object') { + throw new Error('ACCESS_TIME_LABELS must be a dictionary'); + } + + // Sanity check for DEFAULT_ACCESS_TIME + if (accessConfig.DEFAULT_ACCESS_TIME) { + const defaultAccessTime = parseInt(accessConfig.DEFAULT_ACCESS_TIME, 10); + if (isNaN(defaultAccessTime) || !accessConfig.ACCESS_TIME_LABELS.hasOwnProperty(defaultAccessTime)) { + throw new Error('DEFAULT_ACCESS_TIME must be a valid key in ACCESS_TIME_LABELS'); + } + } + + return accessConfig; +} + +const accessConfig = load_access_config(); module.exports = { plugins: [ @@ -6,7 +46,6 @@ module.exports = { plugin: CracoAlias, options: { source: 'tsconfig', - /* tsConfigPath should point to the file where "paths" are specified */ tsConfigPath: './tsconfig.paths.json', }, }, @@ -15,5 +54,10 @@ module.exports = { alias: { '@mui/styled-engine': '@mui/styled-engine-sc', }, + plugins: [ + new webpack.DefinePlugin({ + ACCESS_CONFIG: JSON.stringify(accessConfig), + }), + ], }, }; diff --git a/docker-compose.yml b/docker-compose.yml index 8ff595a..4140f1e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,10 @@ services: discord-access: - build: . + build: + context: . + dockerfile: Dockerfile + # args: + # ACCESS_FILE_CONFIG_PATH: 'src/config/config.empty.json' container_name: discord-access env_file: - .env.production diff --git a/package-lock.json b/package-lock.json index 634bc20..f84f9ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,39 +9,41 @@ "version": "1.0.0", "dependencies": { "@mui/base": "^5.0.0-beta.28", - "@mui/icons-material": "*", - "@mui/lab": "*", - "@mui/material": "*", - "@mui/styled-engine-sc": "*", + "@mui/icons-material": "latest", + "@mui/lab": "latest", + "@mui/material": "latest", + "@mui/styled-engine-sc": "latest", "@mui/x-data-grid": "^6.18.4", - "@mui/x-date-pickers": "*", - "@sentry/react": "*", - "@tanstack/react-query": "*", - "@testing-library/jest-dom": "*", - "@testing-library/react": "*", - "@testing-library/user-event": "*", - "dayjs": "*", - "env-cmd": "*", - "react": "*", - "react-dom": "*", - "react-hook-form": "*", - "react-hook-form-mui": "*", - "react-router-dom": "*", - "styled-components": "*" + "@mui/x-date-pickers": "latest", + "@sentry/react": "latest", + "@tanstack/react-query": "latest", + "@testing-library/jest-dom": "latest", + "@testing-library/react": "latest", + "@testing-library/user-event": "latest", + "dayjs": "latest", + "env-cmd": "latest", + "react": "latest", + "react-dom": "latest", + "react-hook-form": "latest", + "react-hook-form-mui": "latest", + "react-router-dom": "latest", + "styled-components": "latest", + "webpack": "^5.97.1" }, "devDependencies": { - "@craco/craco": "*", - "@sentry/cli": "*", - "@types/node": "*", - "@types/react": "*", - "@types/react-dom": "*", - "@types/styled-components": "*", + "@craco/craco": "latest", + "@sentry/cli": "latest", + "@types/node": "latest", + "@types/react": "latest", + "@types/react-dom": "latest", + "@types/styled-components": "latest", + "@types/webpack": "^5.28.5", "husky": "^8.0.3", "lint-staged": "^15.2.10", "prettier": "^3.2.5", "react-app-alias": "^2.2.2", - "react-scripts": "*", - "typescript": "*" + "react-scripts": "latest", + "typescript": "latest" } }, "node_modules/@adobe/css-tools": { @@ -3704,7 +3706,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -3714,7 +3715,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -5423,17 +5423,26 @@ "version": "8.4.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" }, "node_modules/@types/express": { "version": "4.17.14", @@ -5519,8 +5528,7 @@ "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -5538,7 +5546,6 @@ "version": "20.12.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", "integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==", - "devOptional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -5675,6 +5682,18 @@ "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==", "dev": true }, + "node_modules/@types/webpack": { + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.5.tgz", + "integrity": "sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -5929,148 +5948,148 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -6078,13 +6097,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "license": "Apache-2.0" }, "node_modules/abab": { "version": "2.0.6", @@ -6106,10 +6125,10 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "devOptional": true, + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -6139,15 +6158,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -6227,7 +6237,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6282,7 +6291,6 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, "peerDependencies": { "ajv": "^6.9.1" } @@ -7003,10 +7011,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", - "devOptional": true, + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "funding": [ { "type": "opencollective", @@ -7021,11 +7028,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -7046,8 +7054,7 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/builtin-modules": { "version": "3.3.0", @@ -7155,10 +7162,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001524", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", - "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", - "devOptional": true, + "version": "1.0.30001688", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", + "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==", "funding": [ { "type": "opencollective", @@ -7172,7 +7178,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -7253,7 +7260,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, "engines": { "node": ">=6.0" } @@ -8715,10 +8721,10 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.503", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz", - "integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA==", - "devOptional": true + "version": "1.5.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", + "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", + "license": "ISC" }, "node_modules/emittery": { "version": "0.13.1", @@ -8761,7 +8767,6 @@ "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -8916,8 +8921,7 @@ "node_modules/es-module-lexer": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", - "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", - "dev": true + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==" }, "node_modules/es-shim-unscopables": { "version": "1.0.0", @@ -8946,10 +8950,10 @@ } }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true, + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -9629,7 +9633,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -9641,7 +9644,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "engines": { "node": ">=4.0" } @@ -9680,7 +9682,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -9800,8 +9801,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.2.12", @@ -9834,8 +9834,7 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -10433,8 +10432,7 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/global-modules": { "version": "2.0.0", @@ -10516,8 +10514,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/grapheme-splitter": { "version": "1.0.4", @@ -14018,7 +14015,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -14032,7 +14028,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -14127,8 +14122,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "devOptional": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema": { "version": "0.4.0", @@ -14139,8 +14133,7 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -14585,7 +14578,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, "engines": { "node": ">=6.11.5" } @@ -14931,8 +14923,7 @@ "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "node_modules/merge2": { "version": "1.4.1", @@ -14981,7 +14972,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -14990,7 +14980,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -15198,8 +15187,7 @@ "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/no-case": { "version": "3.0.4", @@ -15275,10 +15263,10 @@ "devOptional": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "devOptional": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -15752,10 +15740,10 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -17413,7 +17401,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, "engines": { "node": ">=6" } @@ -17511,7 +17498,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -19533,7 +19519,6 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -19643,7 +19628,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -19748,7 +19732,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, "dependencies": { "randombytes": "^2.1.0" } @@ -20063,7 +20046,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -20073,7 +20055,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -20618,7 +20599,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -20682,7 +20662,6 @@ "version": "5.31.6", "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", - "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -20700,7 +20679,6 @@ "version": "5.3.10", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", @@ -20733,8 +20711,7 @@ "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/test-exclude": { "version": "6.0.0", @@ -21034,8 +21011,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -21124,10 +21100,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", - "devOptional": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -21142,9 +21117,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -21157,7 +21133,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -21290,7 +21265,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -21318,18 +21292,18 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", - "dev": true, - "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -21623,7 +21597,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, "engines": { "node": ">=10.13.0" } @@ -21632,7 +21605,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -21645,7 +21617,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "engines": { "node": ">=4.0" } @@ -24896,7 +24867,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -24906,7 +24876,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, "requires": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -25883,17 +25852,24 @@ "version": "8.4.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.10.tgz", "integrity": "sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==", - "dev": true, "requires": { "@types/estree": "*", "@types/json-schema": "*" } }, + "@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "@types/express": { "version": "4.17.14", @@ -25979,8 +25955,7 @@ "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", - "dev": true + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "@types/json5": { "version": "0.0.29", @@ -25998,7 +25973,6 @@ "version": "20.12.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", "integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==", - "devOptional": true, "requires": { "undici-types": "~5.26.4" } @@ -26135,6 +26109,17 @@ "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==", "dev": true }, + "@types/webpack": { + "version": "5.28.5", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.5.tgz", + "integrity": "sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw==", + "dev": true, + "requires": { + "@types/node": "*", + "tapable": "^2.2.0", + "webpack": "^5" + } + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -26286,162 +26271,145 @@ } }, "@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "requires": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==" }, "@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==" }, "@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==" }, "@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==" }, "@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==" }, "@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "requires": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", - "dev": true, + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "requires": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "abab": { "version": "2.0.6", @@ -26460,10 +26428,9 @@ } }, "acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "devOptional": true + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==" }, "acorn-globals": { "version": "6.0.0", @@ -26483,13 +26450,6 @@ } } }, - "acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "dev": true, - "requires": {} - }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -26551,7 +26511,6 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -26592,7 +26551,6 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, "requires": {} }, "ansi-escapes": { @@ -27149,15 +27107,14 @@ "dev": true }, "browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", - "devOptional": true, + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "requires": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", - "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" } }, "bser": { @@ -27172,8 +27129,7 @@ "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "builtin-modules": { "version": "3.3.0", @@ -27253,10 +27209,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001524", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz", - "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", - "devOptional": true + "version": "1.0.30001688", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001688.tgz", + "integrity": "sha512-Nmqpru91cuABu/DTCXbM2NSRHzM2uVHfPnhJ/1zEAJx/ILBRVmz3pzH4N7DZqbdG0gWClsCC05Oj0mJ/1AWMbA==" }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", @@ -27315,8 +27270,7 @@ "chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==" }, "ci-info": { "version": "3.6.1", @@ -28416,10 +28370,9 @@ } }, "electron-to-chromium": { - "version": "1.4.503", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz", - "integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA==", - "devOptional": true + "version": "1.5.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", + "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==" }, "emittery": { "version": "0.13.1", @@ -28450,7 +28403,6 @@ "version": "5.17.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", - "dev": true, "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -28571,8 +28523,7 @@ "es-module-lexer": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", - "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", - "dev": true + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==" }, "es-shim-unscopables": { "version": "1.0.0", @@ -28595,10 +28546,9 @@ } }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "escape-html": { "version": "1.0.3", @@ -29092,7 +29042,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, "requires": { "estraverse": "^5.2.0" } @@ -29100,8 +29049,7 @@ "estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" }, "estree-walker": { "version": "1.0.1", @@ -29130,8 +29078,7 @@ "events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "execa": { "version": "5.1.1", @@ -29235,8 +29182,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.12", @@ -29265,8 +29211,7 @@ "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fast-levenshtein": { "version": "2.0.6", @@ -29693,8 +29638,7 @@ "glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "global-modules": { "version": "2.0.0", @@ -29757,8 +29701,7 @@ "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "grapheme-splitter": { "version": "1.0.4", @@ -32469,7 +32412,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, "requires": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -32480,7 +32422,6 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -32551,8 +32492,7 @@ "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "devOptional": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.4.0", @@ -32563,8 +32503,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -32860,8 +32799,7 @@ "loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==" }, "loader-utils": { "version": "2.0.4", @@ -33120,8 +33058,7 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { "version": "1.4.1", @@ -33154,14 +33091,12 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "requires": { "mime-db": "1.52.0" } @@ -33305,8 +33240,7 @@ "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "no-case": { "version": "3.0.4", @@ -33372,10 +33306,9 @@ "devOptional": true }, "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "devOptional": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "normalize-path": { "version": "3.0.0", @@ -33724,10 +33657,9 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { "version": "2.3.1", @@ -34762,8 +34694,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "pure-rand": { "version": "6.0.2", @@ -34818,7 +34749,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "requires": { "safe-buffer": "^5.1.0" } @@ -36345,8 +36275,7 @@ "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-regex-test": { "version": "1.0.0", @@ -36408,7 +36337,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, "requires": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -36495,7 +36423,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, "requires": { "randombytes": "^2.1.0" } @@ -36740,7 +36667,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -36749,8 +36675,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -37173,8 +37098,7 @@ "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==" }, "temp-dir": { "version": "2.0.0", @@ -37216,7 +37140,6 @@ "version": "5.31.6", "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", - "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -37227,8 +37150,7 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" } } }, @@ -37236,7 +37158,6 @@ "version": "5.3.10", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", - "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", @@ -37469,8 +37390,7 @@ "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -37534,20 +37454,18 @@ "dev": true }, "update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", - "devOptional": true, + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" } }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" } @@ -37659,7 +37577,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", - "dev": true, "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -37681,18 +37598,17 @@ "dev": true }, "webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", - "dev": true, - "requires": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "requires": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", @@ -37715,7 +37631,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -37724,8 +37639,7 @@ "estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" } } }, @@ -37909,8 +37823,7 @@ "webpack-sources": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==" }, "websocket-driver": { "version": "0.7.4", diff --git a/package.json b/package.json index fd38ada..488d302 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "react-hook-form": "latest", "react-hook-form-mui": "latest", "react-router-dom": "latest", - "styled-components": "latest" + "styled-components": "latest", + "webpack": "latest" }, "scripts": { "tsc": "./node_modules/.bin/tsc", @@ -51,6 +52,7 @@ "@types/react": "latest", "@types/react-dom": "latest", "@types/styled-components": "latest", + "@types/webpack": "latest", "husky": "^8.0.3", "lint-staged": "^15.2.10", "prettier": "^3.2.5", diff --git a/src/config/accessConfig.ts b/src/config/accessConfig.ts new file mode 100644 index 0000000..6d7b59e --- /dev/null +++ b/src/config/accessConfig.ts @@ -0,0 +1,10 @@ +export interface AccessConfig { + ACCESS_TIME_LABELS: Record; + DEFAULT_ACCESS_TIME: string; +} + +// use the globally-injected ACCESS_CONFIG from src/globals.d.ts, typed to AccessConfig interface +// see src/config/config.default.json for the default config +const accessConfig: AccessConfig = ACCESS_CONFIG as AccessConfig; + +export default accessConfig; diff --git a/src/config/config.default.json b/src/config/config.default.json new file mode 100644 index 0000000..186bfef --- /dev/null +++ b/src/config/config.default.json @@ -0,0 +1,12 @@ +{ + "ACCESS_TIME_LABELS": { + "43200": "12 Hours", + "432000": "5 Days", + "1209600": "Two Weeks", + "2592000": "30 Days", + "7776000": "90 Days", + "indefinite": "Indefinite", + "custom": "Custom" + }, + "DEFAULT_ACCESS_TIME": "1209600" +} diff --git a/src/config/config.empty.json b/src/config/config.empty.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/src/config/config.empty.json @@ -0,0 +1 @@ +{} diff --git a/src/globals.d.ts b/src/globals.d.ts new file mode 100644 index 0000000..e475be5 --- /dev/null +++ b/src/globals.d.ts @@ -0,0 +1 @@ +declare const ACCESS_CONFIG: any; diff --git a/src/pages/groups/AddRoles.tsx b/src/pages/groups/AddRoles.tsx index 606aec3..9f5df6d 100644 --- a/src/pages/groups/AddRoles.tsx +++ b/src/pages/groups/AddRoles.tsx @@ -40,6 +40,7 @@ import {PolymorphicGroup, RoleGroup, RoleMember, OktaUser} from '../../api/apiSc import {canManageGroup, isAccessAdmin, isGroupOwner} from '../../authorization'; import {minTagTime, ownerCantAddSelf, requiredReason} from '../../helpers'; import {useCurrentUser} from '../../authentication'; +import accessConfig from '../../config/accessConfig'; dayjs.extend(IsSameOrBefore); @@ -78,24 +79,10 @@ const GROUP_TYPE_ID_TO_LABELS: Record = { const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; -const UNTIL_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', - indefinite: 'Indefinite', - custom: 'Custom', -} as const; - -const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', -} as const; - +const UNTIL_ID_TO_LABELS: Record = accessConfig.ACCESS_TIME_LABELS; +const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = Object.fromEntries( + Object.entries(UNTIL_ID_TO_LABELS).filter(([key]) => !isNaN(Number(key))), +); const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index) => ({id: id, label: label})); function AddRolesDialog(props: AddRolesDialogProps) { @@ -110,7 +97,7 @@ function AddRolesDialog(props: AddRolesDialogProps) { return out; }, new Array()) ?? []; - const [until, setUntil] = React.useState('1209600'); + const [until, setUntil] = React.useState(accessConfig.DEFAULT_ACCESS_TIME); const [roleSearchInput, setRoleSearchInput] = React.useState(''); const [roles, setRoles] = React.useState>([]); const [requestError, setRequestError] = React.useState(''); @@ -144,7 +131,10 @@ function AddRolesDialog(props: AddRolesDialogProps) { {} as Record, ); - timeLimitUntil = timeLimit >= 1209600 ? '1209600' : Object.keys(filteredUntil).at(-1)!; + timeLimitUntil = + timeLimit >= Number(accessConfig.DEFAULT_ACCESS_TIME) + ? accessConfig.DEFAULT_ACCESS_TIME + : Object.keys(filteredUntil).at(-1)!; labels = Object.entries(Object.assign({}, filteredUntil, {custom: 'Custom'})).map(([id, label], index) => ({ id: id, @@ -242,7 +232,7 @@ function AddRolesDialog(props: AddRolesDialogProps) { return ( props.setOpen(false)}> - defaultValues={timeLimit ? {until: timeLimitUntil!} : {until: '1209600'}} + defaultValues={timeLimit ? {until: timeLimitUntil!} : {until: accessConfig.DEFAULT_ACCESS_TIME}} onSuccess={(formData) => submit(formData)}> Add {addRolesText} diff --git a/src/pages/groups/AddUsers.tsx b/src/pages/groups/AddUsers.tsx index bcf773d..b9e8194 100644 --- a/src/pages/groups/AddUsers.tsx +++ b/src/pages/groups/AddUsers.tsx @@ -47,6 +47,7 @@ import { requiredReason, requiredReasonGroups, } from '../../helpers'; +import accessConfig from '../../config/accessConfig'; dayjs.extend(IsSameOrBefore); @@ -78,30 +79,16 @@ interface AddUsersForm { const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; -const UNTIL_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', - indefinite: 'Indefinite', - custom: 'Custom', -} as const; - -const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', -} as const; - +const UNTIL_ID_TO_LABELS: Record = accessConfig.ACCESS_TIME_LABELS; +const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = Object.fromEntries( + Object.entries(UNTIL_ID_TO_LABELS).filter(([key]) => !isNaN(Number(key))), +); const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index) => ({id: id, label: label})); function AddUsersDialog(props: AddUsersDialogProps) { const navigate = useNavigate(); - const [until, setUntil] = React.useState('1209600'); + const [until, setUntil] = React.useState(accessConfig.DEFAULT_ACCESS_TIME); const [userSearchInput, setUserSearchInput] = React.useState(''); const [users, setUsers] = React.useState>([]); const [requestError, setRequestError] = React.useState(''); @@ -162,7 +149,10 @@ function AddUsersDialog(props: AddUsersDialogProps) { {} as Record, ); - timeLimitUntil = timeLimit >= 1209600 ? '1209600' : Object.keys(filteredUntil).at(-1)!; + timeLimitUntil = + timeLimit >= Number(accessConfig.DEFAULT_ACCESS_TIME) + ? accessConfig.DEFAULT_ACCESS_TIME + : Object.keys(filteredUntil).at(-1)!; labels = Object.entries(Object.assign({}, filteredUntil, {custom: 'Custom'})).map(([id, label], index) => ({ id: id, @@ -244,7 +234,7 @@ function AddUsersDialog(props: AddUsersDialogProps) { return ( props.setOpen(false)}> - defaultValues={timeLimit ? {until: timeLimitUntil!} : {until: '1209600'}} + defaultValues={timeLimit ? {until: timeLimitUntil!} : {until: accessConfig.DEFAULT_ACCESS_TIME}} onSuccess={(formData) => submit(formData)}> Add {addUsersText} diff --git a/src/pages/groups/BulkRenewal.tsx b/src/pages/groups/BulkRenewal.tsx index 2e5eedb..331108d 100644 --- a/src/pages/groups/BulkRenewal.tsx +++ b/src/pages/groups/BulkRenewal.tsx @@ -30,6 +30,7 @@ import {displayUserName, minTagTimeGroups, requiredReasonGroups} from '../../hel import {usePutGroupMembersById, PutGroupMembersByIdError, PutGroupMembersByIdVariables} from '../../api/apiComponents'; import {GroupMember, OktaUserGroupMember, PolymorphicGroup, RoleGroupMap, RoleGroup} from '../../api/apiSchemas'; import BulkRenewalDataGrid from '../../components/BulkRenewalDataGrid'; +import accessConfig from '../../config/accessConfig'; interface Data { id: number; @@ -68,24 +69,10 @@ interface CreateRequestForm { reason?: string; } -const UNTIL_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', - indefinite: 'Indefinite', - custom: 'Custom', -} as const; - -const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', -} as const; - +const UNTIL_ID_TO_LABELS: Record = accessConfig.ACCESS_TIME_LABELS; +const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = Object.fromEntries( + Object.entries(UNTIL_ID_TO_LABELS).filter(([key]) => !isNaN(Number(key))), +); const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index) => ({id: id, label: label})); const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; @@ -113,7 +100,7 @@ function BulkRenewalDialog(props: BulkRenewalDialogProps) { const [selected, setSelected] = React.useState(() => props.select != undefined ? props.rows.filter((r) => r.id == props.select) : [], ); - const [until, setUntil] = React.useState('1209600'); + const [until, setUntil] = React.useState(accessConfig.DEFAULT_ACCESS_TIME); const [selectionModel, setSelectionModel] = React.useState(() => props.rows.filter((r) => r.id == props.select).map((r) => r.id), diff --git a/src/pages/requests/Create.tsx b/src/pages/requests/Create.tsx index e0bf49f..2acaac4 100644 --- a/src/pages/requests/Create.tsx +++ b/src/pages/requests/Create.tsx @@ -42,6 +42,7 @@ import { } from '../../api/apiSchemas'; import {canManageGroup} from '../../authorization'; import {minTagTime, minTagTimeGroups} from '../../helpers'; +import accessConfig from '../../config/accessConfig'; dayjs.extend(IsSameOrBefore); @@ -168,24 +169,10 @@ const GROUP_TYPE_ID_TO_LABELS: Record = { const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; -const UNTIL_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', - indefinite: 'Indefinite', - custom: 'Custom', -} as const; - -const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', -} as const; - +const UNTIL_ID_TO_LABELS: Record = accessConfig.ACCESS_TIME_LABELS; +const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = Object.fromEntries( + Object.entries(UNTIL_ID_TO_LABELS).filter(([key]) => !isNaN(Number(key))), +); const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index) => ({id: id, label: label})); function filterUntilLabels(timeLimit: number): [string, Array>] { @@ -232,7 +219,7 @@ function CreateRequestContainer(props: CreateRequestContainerProps) { const untilLabels: [string, Array>] = timeLimit ? filterUntilLabels(timeLimit) - : ['1209600', UNTIL_OPTIONS]; + : [accessConfig.DEFAULT_ACCESS_TIME, UNTIL_OPTIONS]; const [until, setUntil] = React.useState(untilLabels[0]); const [labels, setLabels] = React.useState>>(untilLabels[1]); @@ -320,7 +307,7 @@ function CreateRequestContainer(props: CreateRequestContainerProps) { defaultValues={{ group: props.group, - until: '1209600', + until: accessConfig.DEFAULT_ACCESS_TIME, ownerOrMember: props.owner != null ? (props.owner ? 'owner' : 'member') : undefined, }} onSuccess={(formData) => submit(formData)}> diff --git a/src/pages/requests/Read.tsx b/src/pages/requests/Read.tsx index 475bf29..06e388d 100644 --- a/src/pages/requests/Read.tsx +++ b/src/pages/requests/Read.tsx @@ -72,6 +72,7 @@ import { import NotFound from '../NotFound'; import Loading from '../../components/Loading'; +import accessConfig from '../../config/accessConfig'; dayjs.extend(RelativeTime); dayjs.extend(IsSameOrBefore); @@ -99,24 +100,10 @@ const GROUP_TYPE_ID_TO_LABELS: Record = { const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; -const UNTIL_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', - indefinite: 'Indefinite', - custom: 'Custom', -} as const; - -const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', -} as const; - +const UNTIL_ID_TO_LABELS: Record = accessConfig.ACCESS_TIME_LABELS; +const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = Object.fromEntries( + Object.entries(UNTIL_ID_TO_LABELS).filter(([key]) => !isNaN(Number(key))), +); const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index) => ({id: id, label: label})); function ComputeConstraints(accessRequest: AccessRequest) { diff --git a/src/pages/roles/AddGroups.tsx b/src/pages/roles/AddGroups.tsx index 38d67c9..a7b1280 100644 --- a/src/pages/roles/AddGroups.tsx +++ b/src/pages/roles/AddGroups.tsx @@ -43,6 +43,7 @@ import {isAccessAdmin, isGroupOwner} from '../../authorization'; import {minTagTimeGroups, requiredReasonGroups, ownerCantAddSelf} from '../../helpers'; import {useCurrentUser} from '../../authentication'; import {group} from 'console'; +import accessConfig from '../../config/accessConfig'; dayjs.extend(IsSameOrBefore); @@ -79,24 +80,10 @@ const GROUP_TYPE_ID_TO_LABELS: Record = { const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; -const UNTIL_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', - indefinite: 'Indefinite', - custom: 'Custom', -} as const; - -const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', -} as const; - +const UNTIL_ID_TO_LABELS: Record = accessConfig.ACCESS_TIME_LABELS; +const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = Object.fromEntries( + Object.entries(UNTIL_ID_TO_LABELS).filter(([key]) => !isNaN(Number(key))), +); const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index) => ({id: id, label: label})); function AddGroupsDialog(props: AddGroupsDialogProps) { @@ -121,7 +108,7 @@ function AddGroupsDialog(props: AddGroupsDialogProps) { props.group.active_user_memberships?.map((membership) => membership.active_user!.id).includes(currentUser.id) ?? false; - const [until, setUntil] = React.useState('1209600'); + const [until, setUntil] = React.useState(accessConfig.DEFAULT_ACCESS_TIME); const [groupSearchInput, setGroupSearchInput] = React.useState(''); const [groups, setGroups] = React.useState>([]); const [requestError, setRequestError] = React.useState(''); diff --git a/src/pages/roles/BulkRenewal.tsx b/src/pages/roles/BulkRenewal.tsx index fc24c36..ee539f6 100644 --- a/src/pages/roles/BulkRenewal.tsx +++ b/src/pages/roles/BulkRenewal.tsx @@ -33,6 +33,7 @@ import {usePutRoleMembersById, PutRoleMembersByIdError, PutRoleMembersByIdVariab import {RoleMember, RoleGroupMap, OktaGroup, AppGroup} from '../../api/apiSchemas'; import {isAccessAdmin} from '../../authorization'; import BulkRenewalDataGrid from '../../components/BulkRenewalDataGrid'; +import accessConfig from '../../config/accessConfig'; interface Data { id: number; @@ -70,25 +71,10 @@ interface CreateRequestForm { customUntil?: string; reason?: string; } - -const UNTIL_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', - indefinite: 'Indefinite', - custom: 'Custom', -} as const; - -const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = { - '43200': '12 Hours', - '432000': '5 Days', - '1209600': 'Two Weeks', - '2592000': '30 Days', - '7776000': '90 Days', -} as const; - +const UNTIL_ID_TO_LABELS: Record = accessConfig.ACCESS_TIME_LABELS; +const UNTIL_JUST_NUMERIC_ID_TO_LABELS: Record = Object.fromEntries( + Object.entries(UNTIL_ID_TO_LABELS).filter(([key]) => !isNaN(Number(key))), +); const UNTIL_OPTIONS = Object.entries(UNTIL_ID_TO_LABELS).map(([id, label], index) => ({id: id, label: label})); const RFC822_FORMAT = 'ddd, DD MMM YYYY HH:mm:ss ZZ'; @@ -116,7 +102,7 @@ function BulkRenewalDialog(props: BulkRenewalDialogProps) { const [labels, setLabels] = React.useState>>(UNTIL_OPTIONS); const [timeLimit, setTimeLimit] = React.useState(null); const [requiredReason, setRequiredReason] = React.useState(false); - const [until, setUntil] = React.useState('1209600'); + const [until, setUntil] = React.useState(accessConfig.DEFAULT_ACCESS_TIME); const [paginationModel, setPaginationModel] = React.useState({ pageSize: 10, diff --git a/tsconfig.json b/tsconfig.json index c39ec14..bcd668e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,9 +22,18 @@ // Set `sourceRoot` to `/` to strip the build path prefix from // generated source code references. This allows Sentry to match source files // relative to your source root folder. - "sourceRoot": "/" + "sourceRoot": "/", + // ensure that ACCESS_CONFIG is made globally available + "typeRoots": [ + "./node_modules/@types", + "./src/globals.d.ts" + ] }, - "include": ["src/**/*", "mui.d.ts"], + "include": [ + "src/**/*", + "mui.d.ts", + "src/globals.d.ts" + ], "paths": { "@mui/styled-engine": ["./node_modules/@mui/styled-engine-sc"] } From 193d08ada4a33d14b5b7821202c8f111bfb6471b Mon Sep 17 00:00:00 2001 From: Rose Toomey Date: Sun, 12 Jan 2025 14:12:58 +0100 Subject: [PATCH 2/3] Move access config load logic out of craco config 1. Move config files to project-level config directory 2. Move logic out of craco.config.js to src/config/loadAccessConfig.js 3. Update README --- .env.production.example | 3 +- .gitignore | 11 +++-- Dockerfile | 3 +- README.md | 71 +++++++++++++++++++++------ config/config.default.json | 15 ++++++ craco.config.js | 41 ++-------------- docker-compose.yml | 2 - src/config/config.default.json | 12 ----- src/config/config.empty.json | 1 - src/config/loadAccessConfig.js | 90 ++++++++++++++++++++++++++++++++++ 10 files changed, 173 insertions(+), 76 deletions(-) create mode 100644 config/config.default.json delete mode 100644 src/config/config.default.json delete mode 100644 src/config/config.empty.json create mode 100644 src/config/loadAccessConfig.js diff --git a/.env.production.example b/.env.production.example index 42dc807..efef470 100644 --- a/.env.production.example +++ b/.env.production.example @@ -10,6 +10,7 @@ REACT_APP_API_SERVER_URL="" FLASK_SENTRY_DSN=https://@sentry.io/ REACT_SENTRY_DSN=https://@sentry.io/ CLOUDFLARE_TEAM_DOMAIN= -CLOUDFLARE_APPLICATION_AUDIENCE= +CLOUDFLARE_APPLICATION_AUDIENCE= SECRET_KEY= OIDC_CLIENT_SECRETS= +ACCESS_CONFIG_FILE= diff --git a/.gitignore b/.gitignore index a5aa6d7..c043a11 100644 --- a/.gitignore +++ b/.gitignore @@ -498,8 +498,9 @@ $RECYCLE.BIN/ # End of https://www.toptal.com/developers/gitignore/api/macos,windows,visualstudiocode,jetbrains+all,node,python,flask -config.production.json -config.staging.json -config.development.json -config.test.json -config.override.json +# try to prevent override config files from being accidentally committed +config/config.production.json +config/config.staging.json +config/config.development.json +config/config.test.json +config/config.override.json diff --git a/Dockerfile b/Dockerfile index 272a99f..d2586e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,7 @@ ENV PATH=/app/node_modules/.bin:$PATH COPY craco.config.js package.json package-lock.json tsconfig.json tsconfig.paths.json .env.production* ./ COPY ./src ./src COPY ./public ./public -ARG ACCESS_FILE_CONFIG_PATH="src/config/config.empty.json" -COPY $ACCESS_FILE_CONFIG_PATH /app/config.override.json +COPY ./config ./config RUN npm install RUN touch .env.production diff --git a/README.md b/README.md index dd17364..aae94d3 100644 --- a/README.md +++ b/README.md @@ -248,28 +248,69 @@ If you are using Cloudflare Access, ensure that you configure `CLOUDFLARE_TEAM_D Else, if you are using a generic OIDC identity provider (such as Okta), then you should configure `SECRET_KEY` and `OIDC_CLIENT_SECRETS`. `CLOUDFLARE_TEAM_DOMAIN` and `CLOUDFLARE_APPLICATION_AUDIENCE` do not need to be set and can be removed from your env file. Make sure to also mount your `client-secrets.json` file to the container if you don't have it inline. -### Access application configuration overrides +### Access application configuration -The default config for the application is at `src/config/config.default.json`. +_All front-end and back-end configuration overrides are **optional**._ -If you want to override those values, create your own config file containing JSON that overrides values in the default config. +The default config for the application is at [`config/config.default.json`](config/config.default.json). -- `ACCESS_TIME_LABELS`: _Optional._ Specifies the time access labels to use for dropdowns on the front end. Contains a JSON object of the format `{"NUM_SECONDS": "LABEL"}`. **Example:** `{"86400": "1 day", "604800": "1 week", "2592000": "1 month"}`. -- `DEFAULT_ACCESS_TIME`: _Optional._ Specifies the default time access label to use for dropdowns on the front end. Contains a string with a number of seconds corresponding to a key in the access time labels, e.g. `"86400"`. +The file is structured with two keys, `FRONTEND` and `BACKEND`, which contain the configuration overrides for the +front-end and back-end respectively. -To use your custom config file, edit `docker-compose.yml` to add a build arg `ACCESS_FILE_CONFIG_PATH` with a local path to your config override file: +If you want to override either front-end or back-end values, create your own config file based on +[`config/config.default.json`](config/config.default.json). Any values that you don't override will fall back to +the values in the default config. + +To use your custom config file, set the `ACCESS_CONFIG_FILE` environment variable to the name of your config +override file in the project-level `config` directory. + +### Sample Usage + +To override environment variables, create an override config file in the `config` directory. (You can name +this file whatever you want because the name of the file is specified by your `ACCESS_CONFIG_FILE` environment +variable.) + +For example, if you want to set the default access time to 5 days in production, you might create a file named +`config.production.json` in the `config` directory: + +```json +{ + "FRONTEND": { + "DEFAULT_ACCESS_TIME": "432000" + } +} +``` + +Then, in your `.env.production` file, set the `ACCESS_CONFIG_FILE` environment variable to the name of your +config file: -```yaml -services: - discord-access: - build: - context: . - dockerfile: Dockerfile - args: - ACCESS_FILE_CONFIG_PATH: 'path/to/config.production.json' ``` +ACCESS_CONFIG_FILE=config.production.json +``` + +This tells the application to use `config.production.json` for configuration overrides. + +#### Frontend Configuration + +To override values on the front-end, modify these key-value pairs inside the `FRONTEND` key in your custom config file. + +| Name | Details | Example | +|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------| +| `ACCESS_TIME_LABELS` | Specifies the time access labels to use for dropdowns on the front end. Contains a JSON object of the format `{"NUM_SECONDS": "LABEL"}`. | `{"86400": "1 day", "604800": "1 week", "2592000": "1 month"}` | +| `DEFAULT_ACCESS_TIME` | Specifies the default time access label to use for dropdowns on the front end. Contains a string with a number of seconds corresponding to a key in the access time labels. | `"86400"` | + +The front-end config is loaded in [`craco.config.js`](craco.config.js). See +[`src/config/loadAccessConfig.js`](src/config/loadAccessConfig.js) for more details. + +#### Backend Configuration + +To override values on the back-end, modify these key-value pairs inside the `BACKEND` key in your custom config file. + +_There are currently no supported back-end configuration overrides but this is planned to change soon._ -If `ACCESS_FILE_CONFIG_PATH` is not set as a build arg when building the Docker image, then the default config will be used. +| Name | Details | Example | +|------|---------|---------| +| TBD | TBD | TBD | #### Database Setup diff --git a/config/config.default.json b/config/config.default.json new file mode 100644 index 0000000..6f5cfb3 --- /dev/null +++ b/config/config.default.json @@ -0,0 +1,15 @@ +{ + "FRONTEND": { + "ACCESS_TIME_LABELS": { + "43200": "12 Hours", + "432000": "5 Days", + "1209600": "Two Weeks", + "2592000": "30 Days", + "7776000": "90 Days", + "indefinite": "Indefinite", + "custom": "Custom" + }, + "DEFAULT_ACCESS_TIME": "1209600" + }, + "BACKEND": {} +} diff --git a/craco.config.js b/craco.config.js index e3161c5..f30032c 100644 --- a/craco.config.js +++ b/craco.config.js @@ -1,44 +1,9 @@ const CracoAlias = require('react-app-alias'); -const fs = require('fs'); const path = require('path'); const webpack = require('webpack'); +const {loadAccessConfig} = require('./src/config/loadAccessConfig'); -function load_access_config() { - // Load the default config - const defaultConfigPath = path.resolve(__dirname, 'src/config/config.default.json'); - const accessConfig = JSON.parse(fs.readFileSync(defaultConfigPath, 'utf8')); - - // Check for config.override.json - const overrideConfigPath = path.resolve(__dirname, 'config.override.json'); - if (fs.existsSync(overrideConfigPath)) { - const overrideConfig = JSON.parse(fs.readFileSync(overrideConfigPath, 'utf8')); - Object.assign(accessConfig, overrideConfig); - } else { - // Check for ACCESS_FILE_CONFIG_PATH environment variable - const envConfigPath = process.env.ACCESS_FILE_CONFIG_PATH; - if (envConfigPath && fs.existsSync(envConfigPath)) { - const envConfig = JSON.parse(fs.readFileSync(envConfigPath, 'utf8')); - Object.assign(accessConfig, envConfig); - } - } - - // Sanity check for ACCESS_TIME_LABELS - if (accessConfig.ACCESS_TIME_LABELS && typeof accessConfig.ACCESS_TIME_LABELS !== 'object') { - throw new Error('ACCESS_TIME_LABELS must be a dictionary'); - } - - // Sanity check for DEFAULT_ACCESS_TIME - if (accessConfig.DEFAULT_ACCESS_TIME) { - const defaultAccessTime = parseInt(accessConfig.DEFAULT_ACCESS_TIME, 10); - if (isNaN(defaultAccessTime) || !accessConfig.ACCESS_TIME_LABELS.hasOwnProperty(defaultAccessTime)) { - throw new Error('DEFAULT_ACCESS_TIME must be a valid key in ACCESS_TIME_LABELS'); - } - } - - return accessConfig; -} - -const accessConfig = load_access_config(); +const accessConfig = loadAccessConfig(); module.exports = { plugins: [ @@ -56,7 +21,7 @@ module.exports = { }, plugins: [ new webpack.DefinePlugin({ - ACCESS_CONFIG: JSON.stringify(accessConfig), + ACCESS_CONFIG: accessConfig, }), ], }, diff --git a/docker-compose.yml b/docker-compose.yml index 4140f1e..f340195 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,8 +3,6 @@ services: build: context: . dockerfile: Dockerfile - # args: - # ACCESS_FILE_CONFIG_PATH: 'src/config/config.empty.json' container_name: discord-access env_file: - .env.production diff --git a/src/config/config.default.json b/src/config/config.default.json deleted file mode 100644 index 186bfef..0000000 --- a/src/config/config.default.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ACCESS_TIME_LABELS": { - "43200": "12 Hours", - "432000": "5 Days", - "1209600": "Two Weeks", - "2592000": "30 Days", - "7776000": "90 Days", - "indefinite": "Indefinite", - "custom": "Custom" - }, - "DEFAULT_ACCESS_TIME": "1209600" -} diff --git a/src/config/config.empty.json b/src/config/config.empty.json deleted file mode 100644 index 0967ef4..0000000 --- a/src/config/config.empty.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/config/loadAccessConfig.js b/src/config/loadAccessConfig.js new file mode 100644 index 0000000..82abf21 --- /dev/null +++ b/src/config/loadAccessConfig.js @@ -0,0 +1,90 @@ +const fs = require('fs'); +const path = require('path'); + +const ACCESS_TIME_LABELS = 'ACCESS_TIME_LABELS'; +const DEFAULT_ACCESS_TIME = 'DEFAULT_ACCESS_TIME'; +const FRONTEND = 'FRONTEND'; + +class UndefinedConfigError extends Error { + constructor(key, obj) { + const message = `'${key}' is not a defined config value in AccessConfig`; + super(message); + this.name = 'UndefinedConfigError'; + } +} + +class AccessConfigValidationError extends Error { + constructor(message) { + super(message); + this.name = 'ValidationError'; + } +} + +class ConfigFileNotFoundError extends Error { + constructor(filePath) { + const message = `Config override file not found: ${filePath}`; + super(message); + this.name = 'ConfigFileNotFoundError'; + } +} + +function getConfig(obj, key) { + if (key in obj) { + return obj[key]; + } else { + throw new UndefinedConfigError(String(key), obj); + } +} + +function validateConfig(accessConfig) { + if (ACCESS_TIME_LABELS in accessConfig && typeof accessConfig[ACCESS_TIME_LABELS] !== 'object') { + throw new AccessConfigValidationError(`${ACCESS_TIME_LABELS} must be a dictionary`); + } + + if (DEFAULT_ACCESS_TIME in accessConfig) { + const defaultAccessTime = parseInt(getConfig(accessConfig, DEFAULT_ACCESS_TIME), 10); + if (isNaN(defaultAccessTime) || !getConfig(accessConfig, ACCESS_TIME_LABELS).hasOwnProperty(defaultAccessTime)) { + throw new AccessConfigValidationError(`${DEFAULT_ACCESS_TIME} must be a valid key in ${ACCESS_TIME_LABELS}`); + } + } +} + +function loadOverrideConfig(accessConfig) { + const envConfigPath = process.env.ACCESS_CONFIG_FILE + ? path.resolve(__dirname, '../../config', process.env.ACCESS_CONFIG_FILE) + : null; + if (envConfigPath) { + if (fs.existsSync(envConfigPath)) { + const envConfig = JSON.parse(fs.readFileSync(envConfigPath, 'utf8')); + if (FRONTEND in envConfig) { + Object.assign(accessConfig, getConfig(envConfig, FRONTEND)); + } + } else { + throw new ConfigFileNotFoundError(envConfigPath); + } + } + return accessConfig; +} + +function loadDefaultConfig() { + const defaultConfigPath = path.resolve(__dirname, '../../config/config.default.json'); + const defaultConfig = JSON.parse(fs.readFileSync(defaultConfigPath, 'utf8')); + + return getConfig(defaultConfig, FRONTEND); +} + +function loadAccessConfig() { + try { + let accessConfig = loadDefaultConfig(); + accessConfig = loadOverrideConfig(accessConfig); + + validateConfig(accessConfig); + + return JSON.stringify(accessConfig); + } catch (error) { + console.error('Error loading access configuration:', error); + throw error; + } +} + +module.exports = {loadAccessConfig}; From 9c09df1ad974328ed4f91aea78e7dae1ef3720e0 Mon Sep 17 00:00:00 2001 From: Rose Toomey Date: Tue, 14 Jan 2025 16:52:14 +0100 Subject: [PATCH 3/3] Make access config available on both front and back ends - add and test access config loader in Flask back-end app - add sample configuration both both front- and back-end: extract name validation pattern to config - update README --- Dockerfile | 1 + README.md | 22 ++-- api/access_config.py | 96 +++++++++++++++ api/views/schemas/core_schemas.py | 34 +++--- config/config.default.json | 9 +- craco.config.js | 1 + docker-compose.yml | 4 +- src/config/accessConfig.ts | 2 + src/config/loadAccessConfig.js | 22 +++- src/pages/apps/CreateUpdate.tsx | 7 +- src/pages/groups/CreateUpdate.tsx | 7 +- src/pages/tags/CreateUpdate.tsx | 7 +- tests/test_access_config.py | 186 ++++++++++++++++++++++++++++++ 13 files changed, 356 insertions(+), 42 deletions(-) create mode 100644 api/access_config.py create mode 100644 tests/test_access_config.py diff --git a/Dockerfile b/Dockerfile index d2586e2..12f3a66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,7 @@ RUN rm ./build/static/js/*.map RUN mkdir ./api && mkdir ./migrations COPY requirements.txt api/ ./api/ COPY migrations/ ./migrations/ +COPY ./config ./config RUN pip install -r ./api/requirements.txt # Build an image that includes the optional sentry release push build step diff --git a/README.md b/README.md index aae94d3..879e8a3 100644 --- a/README.md +++ b/README.md @@ -294,10 +294,12 @@ This tells the application to use `config.production.json` for configuration ove To override values on the front-end, modify these key-value pairs inside the `FRONTEND` key in your custom config file. -| Name | Details | Example | -|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------| -| `ACCESS_TIME_LABELS` | Specifies the time access labels to use for dropdowns on the front end. Contains a JSON object of the format `{"NUM_SECONDS": "LABEL"}`. | `{"86400": "1 day", "604800": "1 week", "2592000": "1 month"}` | -| `DEFAULT_ACCESS_TIME` | Specifies the default time access label to use for dropdowns on the front end. Contains a string with a number of seconds corresponding to a key in the access time labels. | `"86400"` | +| Name | Details | Example | +|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------| +| `ACCESS_TIME_LABELS` | Specifies the time access labels to use for dropdowns on the front end. Contains a JSON object of the format `{"NUM_SECONDS": "LABEL"}`. | `{"86400": "1 day", "604800": "1 week", "2592000": "1 month"}` | +| `DEFAULT_ACCESS_TIME` | Specifies the default time access label to use for dropdowns on the front end. Contains a string with a number of seconds corresponding to a key in the access time labels. | `"86400"` | +| `NAME_VALIDATION_PATTERN` | Specifies the regex pattern to use for validating role, group, and tag names. Should include preceding `^` and trailing `$` but is not a regex literal so omit `/` at beginning and end of the pattern | `"^[a-zA-Z0-9-]*$"` | +| `NAME_VALIDATION_ERROR` | Specifies the error message to display when a name does not match the validation pattern. | `"Name must contain only letters, numbers, and underscores."` | The front-end config is loaded in [`craco.config.js`](craco.config.js). See [`src/config/loadAccessConfig.js`](src/config/loadAccessConfig.js) for more details. @@ -306,11 +308,15 @@ The front-end config is loaded in [`craco.config.js`](craco.config.js). See To override values on the back-end, modify these key-value pairs inside the `BACKEND` key in your custom config file. -_There are currently no supported back-end configuration overrides but this is planned to change soon._ +| Name | Details | Example | +|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| +| `NAME_VALIDATION_PATTERN` | PCRE regex used for validating role, group, and tag names. Should not explicitly declare pattern boundaries: depending on context, may be used with or without a preceding `^` and a trailing `$`. | `[A-Z][A-Za-z0-9-]*` | +| `NAME_VALIDATION_ERROR` | Error message to display when a name does not match the validation pattern. | `Name must start with a capital letter and contain only letters, numbers, and hypens.` | -| Name | Details | Example | -|------|---------|---------| -| TBD | TBD | TBD | +The back-end config is loaded in [`api/access_config.py`](api/access_config.py). + +See [`api/views/schemas/core_schemas.py`](api/views/schemas/core_schemas.py) for details about how the pattern override +supplied here will be used. #### Database Setup diff --git a/api/access_config.py b/api/access_config.py new file mode 100644 index 0000000..b7be842 --- /dev/null +++ b/api/access_config.py @@ -0,0 +1,96 @@ +import json +import logging +import os +from typing import Any + +logger = logging.getLogger(__name__) + +# Define constants for AccessConfig JSON keys +BACKEND = "BACKEND" +NAME_VALIDATION_PATTERN = "NAME_VALIDATION_PATTERN" +NAME_VALIDATION_ERROR = "NAME_VALIDATION_ERROR" + + +class UndefinedConfigKeyError(Exception): + def __init__(self, key: str, config: dict[str, Any]): + super().__init__(f"'{key}' is not a defined config value in: {sorted(config.keys())}") + + +class ConfigFileNotFoundError(Exception): + def __init__(self, file_path: str): + super().__init__(f"Config override file not found: {file_path}") + + +class ConfigValidationError(Exception): + def __init__(self, error: str): + super().__init__(f"Config validation failed: {error}") + + +class AccessConfig: + def __init__(self, name_pattern: str, name_validation_error: str): + self.name_pattern = name_pattern + self.name_validation_error = name_validation_error + + +def _get_config_value(config: dict[str, Any], key: str) -> Any: + if key in config: + return config[key] + else: + raise UndefinedConfigKeyError(key, config) + + +def _validate_override_config(config: dict[str, Any]) -> None: + if (NAME_VALIDATION_PATTERN in config) != (NAME_VALIDATION_ERROR in config): + raise ConfigValidationError( + f"If either {NAME_VALIDATION_PATTERN} or {NAME_VALIDATION_ERROR} is present, the other must also be present." + ) + + +def _merge_override_config(config: dict[str, Any], top_level_dir: str) -> None: + access_config_file = os.getenv("ACCESS_CONFIG_FILE") + if access_config_file: + override_config_path = os.path.join(top_level_dir, "config", access_config_file) + if os.path.exists(override_config_path): + logger.debug(f"Loading access config override from {override_config_path}") + with open(override_config_path, "r") as f: + override_config = json.load(f).get(BACKEND, {}) + _validate_override_config(override_config) + config.update(override_config) + else: + raise ConfigFileNotFoundError(str(override_config_path)) + + +def _load_default_config(top_level_dir: str) -> dict[str, Any]: + default_config_path = os.path.join(top_level_dir, "config", "config.default.json") + if not os.path.exists(default_config_path): + raise ConfigFileNotFoundError(str(default_config_path)) + with open(default_config_path, "r") as f: + config = json.load(f).get(BACKEND, {}) + return config + + +def _load_access_config() -> AccessConfig: + top_level_dir = os.path.dirname(os.path.dirname(__file__)) + config = _load_default_config(top_level_dir) + _merge_override_config(config, top_level_dir) + + name_pattern = _get_config_value(config, NAME_VALIDATION_PATTERN) + name_validation_error = _get_config_value(config, NAME_VALIDATION_ERROR) + + return AccessConfig( + name_pattern=name_pattern, + name_validation_error=name_validation_error, + ) + + +_ACCESS_CONFIG = None + + +def get_access_config() -> AccessConfig: + global _ACCESS_CONFIG + if _ACCESS_CONFIG is None: + _ACCESS_CONFIG = _load_access_config() + return _ACCESS_CONFIG + + +__all__ = ["get_access_config", "AccessConfig"] diff --git a/api/views/schemas/core_schemas.py b/api/views/schemas/core_schemas.py index 20a0895..9a7a317 100644 --- a/api/views/schemas/core_schemas.py +++ b/api/views/schemas/core_schemas.py @@ -5,7 +5,7 @@ from marshmallow.schema import SchemaMeta, SchemaOpts from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, auto_field from sqlalchemy.orm import Session - +from api.access_config import get_access_config from api.extensions import db from api.models import ( AccessRequest, @@ -22,6 +22,8 @@ Tag, ) +access_config = get_access_config() + # See https://stackoverflow.com/a/58646612 class OktaUserGroupMemberSchema(SQLAlchemyAutoSchema): @@ -246,9 +248,8 @@ class OktaGroupSchema(SQLAlchemyAutoSchema): validate=validate.And( validate.Length(min=1, max=255), validate.Regexp( - "^[A-Z][A-Za-z0-9-]*$", - error="Group name must start capitalized and contain only alphanumeric characters or hyphens. " - "Regex to match: /{regex}/", + f"^{access_config.name_pattern}$", + error=f"Group {access_config.name_validation_error} Regex to match: /{{regex}}/", ), ), ) @@ -618,9 +619,8 @@ class RoleGroupSchema(SQLAlchemyAutoSchema): validate=validate.And( validate.Length(min=1, max=255), validate.Regexp( - f"^{RoleGroup.ROLE_GROUP_NAME_PREFIX}[A-Z][A-Za-z0-9-]*$", - error="Role name must start capitalized and contain only alphanumeric characters or hyphens. " - "Regex to match: /{regex}/", + f"^{RoleGroup.ROLE_GROUP_NAME_PREFIX}{access_config.name_pattern}$", + error=f"Role {access_config.name_validation_error} Regex to match: /{{regex}}/", ), ), ) @@ -838,9 +838,8 @@ class AppGroupSchema(SQLAlchemyAutoSchema): validate=validate.And( validate.Length(min=1, max=255), validate.Regexp( - f"^{AppGroup.APP_GROUP_NAME_PREFIX}[A-Z][A-Za-z0-9-]*{AppGroup.APP_NAME_GROUP_NAME_SEPARATOR}[A-Z][A-Za-z0-9-]*$", - error="Group name must start capitalized and contain only alphanumeric characters or hyphens. " - "Regex to match: /{regex}/", + f"^{AppGroup.APP_GROUP_NAME_PREFIX}{access_config.name_pattern}{AppGroup.APP_NAME_GROUP_NAME_SEPARATOR}{access_config.name_pattern}$", + error=f"Group {access_config.name_validation_error} Regex to match: /{{regex}}/", ), ), ) @@ -1130,9 +1129,8 @@ class InitialAppGroupSchema(Schema): validate=validate.And( validate.Length(min=1, max=255), validate.Regexp( - f"^{AppGroup.APP_GROUP_NAME_PREFIX}[A-Z][A-Za-z0-9-]*{AppGroup.APP_NAME_GROUP_NAME_SEPARATOR}[A-Z][A-Za-z0-9-]*$", - error="Group name must start capitalized and contain only alphanumeric characters or hyphens. " - "Regex to match: /{regex}/", + f"^{AppGroup.APP_GROUP_NAME_PREFIX}{access_config.name_pattern}{AppGroup.APP_NAME_GROUP_NAME_SEPARATOR}{access_config.name_pattern}$", + error=f"Group {access_config.name_validation_error} Regex to match: /{{regex}}/", ), ), ) @@ -1145,9 +1143,8 @@ class AppSchema(SQLAlchemyAutoSchema): validate=validate.And( validate.Length(min=1, max=255), validate.Regexp( - "^[A-Z][A-Za-z0-9-]*$", - error="App name must start capitalized and contain only alphanumeric characters or hyphens. " - "Regex to match: /{regex}/", + f"^{access_config.name_pattern}$", + error=f"App {access_config.name_validation_error} Regex to match: /{{regex}}/", ), ), ) @@ -1460,9 +1457,8 @@ class TagSchema(SQLAlchemyAutoSchema): validate=validate.And( validate.Length(min=1, max=255), validate.Regexp( - "^[A-Z][A-Za-z0-9-]*$", - error="Tag name must start capitalized and contain only alphanumeric characters or hyphens. " - "Regex to match: /{regex}/", + f"^{access_config.name_pattern}$", + error=f"Tag {access_config.name_validation_error} Regex to match: /{{regex}}/", ), ), ) diff --git a/config/config.default.json b/config/config.default.json index 6f5cfb3..3a075a4 100644 --- a/config/config.default.json +++ b/config/config.default.json @@ -9,7 +9,12 @@ "indefinite": "Indefinite", "custom": "Custom" }, - "DEFAULT_ACCESS_TIME": "1209600" + "DEFAULT_ACCESS_TIME": "1209600", + "NAME_VALIDATION_PATTERN": "^[A-Z][A-Za-z0-9\\-]*$", + "NAME_VALIDATION_ERROR": "Name must start capitalized and contain only alphanumeric characters or hyphens." }, - "BACKEND": {} + "BACKEND": { + "NAME_VALIDATION_PATTERN": "[A-Z][A-Za-z0-9-]*", + "NAME_VALIDATION_ERROR": "name must start capitalized and contain only alphanumeric characters or hyphens." + } } diff --git a/craco.config.js b/craco.config.js index f30032c..484ce07 100644 --- a/craco.config.js +++ b/craco.config.js @@ -11,6 +11,7 @@ module.exports = { plugin: CracoAlias, options: { source: 'tsconfig', + /* tsConfigPath should point to the file where "paths" are specified */ tsConfigPath: './tsconfig.paths.json', }, }, diff --git a/docker-compose.yml b/docker-compose.yml index f340195..8ff595a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,6 @@ services: discord-access: - build: - context: . - dockerfile: Dockerfile + build: . container_name: discord-access env_file: - .env.production diff --git a/src/config/accessConfig.ts b/src/config/accessConfig.ts index 6d7b59e..e533d7a 100644 --- a/src/config/accessConfig.ts +++ b/src/config/accessConfig.ts @@ -1,6 +1,8 @@ export interface AccessConfig { ACCESS_TIME_LABELS: Record; DEFAULT_ACCESS_TIME: string; + NAME_VALIDATION_PATTERN: string; + NAME_VALIDATION_ERROR: string; } // use the globally-injected ACCESS_CONFIG from src/globals.d.ts, typed to AccessConfig interface diff --git a/src/config/loadAccessConfig.js b/src/config/loadAccessConfig.js index 82abf21..1826b35 100644 --- a/src/config/loadAccessConfig.js +++ b/src/config/loadAccessConfig.js @@ -1,22 +1,27 @@ +/* + * JS used in `craco.config.js` to load `ACCESS_CONFIG` as a global variable in the frontend + * If you want to use `AccessConfig` in the frontend, use `accessConfig` in `accessConfig.ts` + * */ + const fs = require('fs'); const path = require('path'); const ACCESS_TIME_LABELS = 'ACCESS_TIME_LABELS'; const DEFAULT_ACCESS_TIME = 'DEFAULT_ACCESS_TIME'; const FRONTEND = 'FRONTEND'; +const NAME_VALIDATION_PATTERN = 'NAME_VALIDATION_PATTERN'; +const NAME_VALIDATION_ERROR = 'NAME_VALIDATION_ERROR'; class UndefinedConfigError extends Error { constructor(key, obj) { const message = `'${key}' is not a defined config value in AccessConfig`; super(message); - this.name = 'UndefinedConfigError'; } } class AccessConfigValidationError extends Error { constructor(message) { super(message); - this.name = 'ValidationError'; } } @@ -24,7 +29,6 @@ class ConfigFileNotFoundError extends Error { constructor(filePath) { const message = `Config override file not found: ${filePath}`; super(message); - this.name = 'ConfigFileNotFoundError'; } } @@ -49,6 +53,14 @@ function validateConfig(accessConfig) { } } +function validate_override_config(overrideConfig) { + if (NAME_VALIDATION_PATTERN in overrideConfig && !(NAME_VALIDATION_ERROR in overrideConfig)) { + throw new AccessConfigValidationError( + `If ${NAME_VALIDATION_PATTERN} is present, ${NAME_VALIDATION_ERROR} must also be overridden.`, + ); + } +} + function loadOverrideConfig(accessConfig) { const envConfigPath = process.env.ACCESS_CONFIG_FILE ? path.resolve(__dirname, '../../config', process.env.ACCESS_CONFIG_FILE) @@ -57,7 +69,9 @@ function loadOverrideConfig(accessConfig) { if (fs.existsSync(envConfigPath)) { const envConfig = JSON.parse(fs.readFileSync(envConfigPath, 'utf8')); if (FRONTEND in envConfig) { - Object.assign(accessConfig, getConfig(envConfig, FRONTEND)); + const frontendConfig = getConfig(envConfig, FRONTEND); + validate_override_config(frontendConfig); + Object.assign(accessConfig, frontendConfig); } } else { throw new ConfigFileNotFoundError(envConfigPath); diff --git a/src/pages/apps/CreateUpdate.tsx b/src/pages/apps/CreateUpdate.tsx index 5b353ea..1d3176c 100644 --- a/src/pages/apps/CreateUpdate.tsx +++ b/src/pages/apps/CreateUpdate.tsx @@ -28,6 +28,7 @@ import { } from '../../api/apiComponents'; import {App, AppTagMap, OktaUser, Tag} from '../../api/apiSchemas'; import {isAccessAdmin, isAppOwnerGroupOwner, ACCESS_APP_RESERVED_NAME} from '../../authorization'; +import accessConfig from '../../config/accessConfig'; interface AppButtonProps { setOpen(open: boolean): any; @@ -146,7 +147,7 @@ function AppDialog(props: AppDialogProps) { variant="outlined" validation={{ maxLength: 255, - pattern: /^[A-Z][A-Za-z0-9\-]*$/, + pattern: new RegExp(accessConfig.NAME_VALIDATION_PATTERN), }} disabled={props.access_app} parseError={(error) => { @@ -157,7 +158,9 @@ function AppDialog(props: AppDialogProps) { return 'Name can be at most 255 characters in length'; } if (error.type == 'pattern') { - return 'Name must start capitalized and contain only alphanumeric characters or hyphens. Regex to match /^[A-Z][A-Za-z0-9-]*$/'; + return ( + accessConfig.NAME_VALIDATION_ERROR + ' Regex to match: ' + accessConfig.NAME_VALIDATION_PATTERN + ); } return ''; diff --git a/src/pages/groups/CreateUpdate.tsx b/src/pages/groups/CreateUpdate.tsx index eeaf3b3..1053d4f 100644 --- a/src/pages/groups/CreateUpdate.tsx +++ b/src/pages/groups/CreateUpdate.tsx @@ -31,6 +31,7 @@ import { } from '../../api/apiComponents'; import {PolymorphicGroup, AppGroup, App, OktaUser, Tag, OktaGroupTagMap} from '../../api/apiSchemas'; import {canManageGroup, isAccessAdmin, isAppOwnerGroupOwner} from '../../authorization'; +import accessConfig from '../../config/accessConfig'; interface GroupButtonProps { defaultGroupType: 'okta_group' | 'app_group' | 'role_group'; @@ -261,7 +262,7 @@ function GroupDialog(props: GroupDialogProps) { disabled={props.app_owner_group} validation={{ maxLength: 255, - pattern: /^[A-Z][A-Za-z0-9\-]*$/, + pattern: new RegExp(accessConfig.NAME_VALIDATION_PATTERN), }} parseError={(error) => { if (error?.message != '') { @@ -271,7 +272,9 @@ function GroupDialog(props: GroupDialogProps) { return 'Name can be at most 255 characters in length'; } if (error.type == 'pattern') { - return 'Name must start capitalized and contain only alphanumeric characters or hyphens. Regex to match /^[A-Z][A-Za-z0-9-]*$/'; + return ( + accessConfig.NAME_VALIDATION_ERROR + ' Regex to match: ' + accessConfig.NAME_VALIDATION_PATTERN + ); } return ''; diff --git a/src/pages/tags/CreateUpdate.tsx b/src/pages/tags/CreateUpdate.tsx index 1e7cf60..8c241fa 100644 --- a/src/pages/tags/CreateUpdate.tsx +++ b/src/pages/tags/CreateUpdate.tsx @@ -29,6 +29,7 @@ import { import NumberInput from '../../components/NumberInput'; import {OktaUser, Tag} from '../../api/apiSchemas'; import {isAccessAdmin} from '../../authorization'; +import accessConfig from '../../config/accessConfig'; interface TagButtonProps { setOpen(open: boolean): any; @@ -200,7 +201,7 @@ function TagDialog(props: TagDialogProps) { variant="outlined" validation={{ maxLength: 255, - pattern: /^[A-Z][A-Za-z0-9\-]*$/, + pattern: new RegExp(accessConfig.NAME_VALIDATION_PATTERN), }} parseError={(error) => { if (error?.message != '') { @@ -210,7 +211,9 @@ function TagDialog(props: TagDialogProps) { return 'Name can be at most 255 characters in length'; } if (error.type == 'pattern') { - return 'Name must start capitalized and contain only alphanumeric characters or hyphens. Regex to match /^[A-Z][A-Za-z0-9-]*$/'; + return ( + accessConfig.NAME_VALIDATION_ERROR + ' Regex to match: ' + accessConfig.NAME_VALIDATION_PATTERN + ); } return ''; }} diff --git a/tests/test_access_config.py b/tests/test_access_config.py new file mode 100644 index 0000000..184edad --- /dev/null +++ b/tests/test_access_config.py @@ -0,0 +1,186 @@ +import json +import os +import tempfile +from typing import Generator, Any +from unittest.mock import patch + +import pytest + +from api.access_config import ( + _load_access_config, + AccessConfig, + ConfigFileNotFoundError, + UndefinedConfigKeyError, + _load_default_config, + _get_config_value, + BACKEND, + _merge_override_config, + NAME_VALIDATION_PATTERN, + NAME_VALIDATION_ERROR, + ConfigValidationError, + _validate_override_config, +) + + +@pytest.fixture +def mock_load_default_config() -> Generator[Any, Any, Any]: + with patch( + "api.access_config._load_default_config", + return_value={ + NAME_VALIDATION_PATTERN: "name_pattern", + NAME_VALIDATION_ERROR: "name_error", + }, + ): + yield + + +@pytest.fixture +def mock_merge_override_config() -> Generator[Any, Any, Any]: + with patch("api.access_config._merge_override_config") as mock_merge: + mock_merge.side_effect = lambda config, _: config.update( + { + NAME_VALIDATION_PATTERN: "override_name_pattern", + NAME_VALIDATION_ERROR: "override_name_error", + } + ) + yield mock_merge + + +def test_load_config_default(mock_load_default_config: None) -> None: + config = _load_access_config() + assert isinstance(config, AccessConfig) + assert config.name_pattern == "name_pattern" + assert config.name_validation_error == "name_error" + + +def test_load_config_with_override(mock_load_default_config: None, mock_merge_override_config: None) -> None: + config = _load_access_config() + assert isinstance(config, AccessConfig) + assert config.name_pattern == "override_name_pattern" + assert config.name_validation_error == "override_name_error" + + +def test_load_default_config() -> None: + with tempfile.TemporaryDirectory() as temp_dir: + config_dir = os.path.join(temp_dir, "config") + os.makedirs(config_dir) + + config_file_path = os.path.join(config_dir, "config.default.json") + with open(config_file_path, "w") as config_file: + json.dump( + { + BACKEND: { + NAME_VALIDATION_PATTERN: "name_pattern", + NAME_VALIDATION_ERROR: "name_error", + }, + }, + config_file, + ) + + config = _load_default_config(temp_dir) + assert config[NAME_VALIDATION_PATTERN] == "name_pattern" + assert config[NAME_VALIDATION_ERROR] == "name_error" + + +def test_merge_override_config(monkeypatch: pytest.MonkeyPatch) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + config_dir = os.path.join(temp_dir, "config") + os.makedirs(config_dir) + + filename = "override_config1.json" + override_config_path = os.path.join(config_dir, filename) + override_config = { + BACKEND: { + NAME_VALIDATION_PATTERN: "override_name_pattern", + NAME_VALIDATION_ERROR: "override_name_error", + } + } + with open(override_config_path, "w") as config_file: + json.dump(override_config, config_file) + + # Mock the ACCESS_CONFIG_FILE environment variable + monkeypatch.setenv("ACCESS_CONFIG_FILE", filename) + + config = { + NAME_VALIDATION_PATTERN: "group_pattern", + NAME_VALIDATION_ERROR: "name_error", + } + + _merge_override_config(config, temp_dir) + + assert config[NAME_VALIDATION_PATTERN] == "override_name_pattern" + assert config[NAME_VALIDATION_ERROR] == "override_name_error" + + +def test_load_default_config_file_not_found() -> None: + with pytest.raises(ConfigFileNotFoundError): + _load_default_config("/non/existent/path") + + +def test_merge_override_config_ignores_frontend_override( + monkeypatch: pytest.MonkeyPatch, +) -> None: + with tempfile.TemporaryDirectory() as temp_dir: + config_dir = os.path.join(temp_dir, "config") + os.makedirs(config_dir) + + filename = "override_config2.json" + override_config_path = os.path.join(config_dir, filename) + override_config = { + "FRONTEND": { + # even if the key in FRONTEND has the same name as a key in BACKEND, it should not override the back-end value + NAME_VALIDATION_PATTERN: "override_name_pattern", + NAME_VALIDATION_ERROR: "override_name_error", + "FOO": "bar", + } + } + with open(override_config_path, "w") as config_file: + json.dump(override_config, config_file) + + # Mock the ACCESS_CONFIG_FILE environment variable + monkeypatch.setenv("ACCESS_CONFIG_FILE", filename) + + config = { + NAME_VALIDATION_PATTERN: "name_pattern", + NAME_VALIDATION_ERROR: "name_error", + } + _merge_override_config(config, temp_dir) + # no overrides from FRONTEND keys! + assert config[NAME_VALIDATION_PATTERN] == "name_pattern" + assert config[NAME_VALIDATION_ERROR] == "name_error" + # extra key from FRONTEND not there either + assert "FOO" not in config + + +def test_get_config_value_raises_undefined_config_key_error() -> None: + config = { + NAME_VALIDATION_PATTERN: "name_pattern", + NAME_VALIDATION_ERROR: "name_error", + } + + with pytest.raises(UndefinedConfigKeyError) as exc_info: + _get_config_value(config, "NON_EXISTENT_KEY") + + assert ( + str(exc_info.value) + == "'NON_EXISTENT_KEY' is not a defined config value in: ['NAME_VALIDATION_ERROR', 'NAME_VALIDATION_PATTERN']" + ) + + +def test_validate_override_config_raises_error_on_partial_override() -> None: + expected_error_msg = f"Config validation failed: If either {NAME_VALIDATION_PATTERN} or {NAME_VALIDATION_ERROR} is present, the other must also be present." + with pytest.raises(ConfigValidationError) as exc_info: + _validate_override_config( + { + NAME_VALIDATION_PATTERN: "override_name_pattern", + } + ) + assert str(exc_info.value) == expected_error_msg + + with pytest.raises(ConfigValidationError) as exc_info: + _validate_override_config( + { + NAME_VALIDATION_ERROR: "override_name_error", + } + ) + assert str(exc_info.value) == expected_error_msg