diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..31d07b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.rs] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a2428fa --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +# Each app has its own .env file, see each .env.example for more information. +# We are following the Turborepo recommendation: https://turbo.build/repo/docs/crafting-your-repository/using-environment-variables#use-env-files-in-packages diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..e928b86 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +// This configuration only applies to the package manager root. +/** @type {import("eslint").Linter.Config} */ +module.exports = { + ignorePatterns: ["apps/**", "packages/**"], + extends: ["@repo/eslint-config/library.js"], + parser: "@typescript-eslint/parser", + parserOptions: { + project: true, + }, +}; diff --git a/.github/actions/setup-dfx/action.yml b/.github/actions/setup-dfx/action.yml new file mode 100644 index 0000000..5ead4ec --- /dev/null +++ b/.github/actions/setup-dfx/action.yml @@ -0,0 +1,35 @@ +name: Setup DFX +description: Setup DFX + +inputs: + dfx-identity-pem: + description: "The DFX identity PEM content" + +runs: + using: "composite" + steps: + - name: Setup DFX + uses: dfinity/setup-dfx@main + with: + dfx-version: "auto" + + - name: Import DFX identity + shell: bash + run: | + if [ -n "${{ inputs.dfx-identity-pem }}" ]; then + echo "${{ inputs.dfx-identity-pem }}" > ./identity.pem + dfx identity import --ic --storage-mode plaintext ci_identity ./identity.pem + dfx identity use ci_identity + else + echo "No DFX identity PEM provided, skipping identity import." + fi + + # just to check if the identity is imported correctly + - name: Print principal + shell: bash + run: | + if [ -n "${{ inputs.dfx-identity-pem }}" ]; then + dfx identity get-principal + else + echo "No DFX identity PEM provided, skipping principal print." + fi diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml new file mode 100644 index 0000000..a2f672a --- /dev/null +++ b/.github/actions/setup-node/action.yml @@ -0,0 +1,41 @@ +name: Setup NodeJS +description: Setup NodeJS + +runs: + using: "composite" + steps: + - name: Setup Turbo cache + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-${{ github.job }}-turbo-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-${{ github.job }}-turbo- + + - name: Install NodeJS + uses: actions/setup-node@v4 + with: + node-version-file: ".node-version" + registry-url: "https://registry.npmjs.org" + + - uses: pnpm/action-setup@v3 + name: Install pnpm + id: pnpm-install + with: + run_install: false + + - name: Get pnpm store directory + shell: bash + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + shell: bash + run: pnpm i --frozen-lockfile diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml new file mode 100644 index 0000000..5d30175 --- /dev/null +++ b/.github/actions/setup-rust/action.yml @@ -0,0 +1,18 @@ +name: Setup Rust +description: Setup Rust +inputs: + cache-key: + description: "The key to use for caching" + required: true + +runs: + using: "composite" + steps: + - name: Install Rust toolchain + shell: bash + run: rustup show + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + shared-key: ${{ inputs.cache-key }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..970f2eb --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,66 @@ +name: Deployments + +on: + # Enable this when you are ready to deploy using the CI/CD workflow + # workflow_run: + # workflows: ["Unit and Integration Tests"] + # types: + # - completed + # branches: + # - main + workflow_dispatch: + +jobs: + check-workflow: + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') }} + steps: + - name: Workflow check + run: exit 0 + + deploy: + needs: + - check-workflow + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-node + + - uses: ./.github/actions/setup-rust + with: + cache-key: "build-wasm" + + - uses: ./.github/actions/setup-dfx + with: + dfx-identity-pem: ${{ secrets.DFX_MAINNET_PEM }} + + - name: Deploy SSP Backend + run: pnpm turbo run deploy --filter=ssp_backend + env: + ID_TOKEN_ISSUER_BASE_URL: https://${{ vars.AUTH0_DOMAIN }}/ + ID_TOKEN_AUDIENCE: ${{ vars.MOBILE_APP_AUTH0_CLIENT_ID }} + DFX_NETWORK: ${{ vars.DFX_NETWORK }} + + deploy-auth0: + needs: + - check-workflow + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-node + + - name: Deploy Auth0 + run: pnpm turbo run deploy --filter=auth0 + env: + AUTH0_DOMAIN: ${{ vars.AUTH0_DOMAIN }} + AUTH0_CLIENT_ID: ${{ vars.AUTH0_DEPLOY_CLI_CLIENT_ID }} + AUTH0_CLIENT_SECRET: ${{ secrets.AUTH0_DEPLOY_CLI_CLIENT_SECRET }} + AUTH0_PRESERVE_KEYWORDS: true + AUTH0_ALLOW_DELETE: true + WEB_APP_URL: ${{ vars.WEB_APP_URL }} + HASURA_GRAPHQL_ENDPOINT: ${{ vars.HASURA_GRAPHQL_ENDPOINT }} + HASURA_GRAPHQL_ADMIN_SECRET: ${{ secrets.HASURA_GRAPHQL_ADMIN_SECRET }} diff --git a/.github/workflows/format-lint.yml b/.github/workflows/format-lint.yml new file mode 100644 index 0000000..5386253 --- /dev/null +++ b/.github/workflows/format-lint.yml @@ -0,0 +1,38 @@ +name: Format and Lint checks + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-node + + - uses: ./.github/actions/setup-rust + with: + cache-key: "fmt" + + - name: Run format command + run: pnpm format:check + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-node + + - uses: ./.github/actions/setup-rust + with: + cache-key: "clippy" + + - name: Run Turborepo lint command + run: pnpm lint diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..aecb62a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,26 @@ +name: Unit and Integration Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: ./.github/actions/setup-node + + - uses: ./.github/actions/setup-rust + with: + cache-key: "build-wasm" + + - uses: ./.github/actions/setup-dfx + + - name: Run Turborepo tests + run: pnpm test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83ba313 --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +node_modules +.pnp +.pnp.js + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist +web-build/ + + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + +# Expo +.expo/ + +# Native +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem +**/.DS_Store + +# typescript +*.tsbuildinfo + +# Internet Computer +.dfx/ +pocket-ic + +# generated files +**/declarations/ + +# rust +target/ + +# Binaries +bin/ diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +20 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..bf4361b --- /dev/null +++ b/.npmrc @@ -0,0 +1,4 @@ + +auto-install-peers=true +legacy-peer-deps=true +node-linker=hoisted diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6560352 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,17 @@ +pnpm-lock.yaml + +packages/ssp-backend/src/generated/ + +apps/auth0/config/**/*.yaml +apps/auth0/config/**/*.yml + +apps/mobile/android/ +apps/mobile/ios/ + +apps/backend/schemaspy/ +apps/backend/hasura/metadata/ + +packages/graphql/src/generated/ +packages/graphql/graphql.schema.json + +apps/storybook/storybook-static \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..31222d5 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,13 @@ +{ + "recommendations": [ + "rust-lang.rust-analyzer", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "expo.vscode-expo-tools", + "dfinity-foundation.vscode-motoko", + "graphql.vscode-graphql", + "graphql.vscode-graphql-syntax", + "styled-components.vscode-styled-components", + "lokalise.i18n-ally" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8012d31 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "editor.formatOnSave": true, + "eslint.workingDirectories": [ + { + "mode": "auto" + } + ], + "rust-analyzer.cargo.extraEnv": { + // just to avoid rust-analyzer env variables errors + "ID_TOKEN_ISSUER_BASE_URL": "https://dummy-issuer-url/", + "ID_TOKEN_AUDIENCE": "dummy-audience" + }, + "rust-analyzer.runnables.extraEnv": { + "ID_TOKEN_ISSUER_BASE_URL": "https://test-issuer-url.local/", + "ID_TOKEN_AUDIENCE": "test-audience" + }, + "rust-analyzer.cargo.features": "all", + "i18n-ally.localesPaths": ["apps/web/messages"], + "i18n-ally.keystyle": "nested" +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4f009f9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3779 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anyhow" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bindgen" +version = "0.68.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726e4313eb6ec35d2730258ad4e15b547ee75d6afaa1361a922e78e59b7d8078" +dependencies = [ + "bitflags 2.5.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn 2.0.55", +] + +[[package]] +name = "binread" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" +dependencies = [ + "binread_derive", + "lazy_static", + "rustversion", +] + +[[package]] +name = "binread_derive" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" +dependencies = [ + "either", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "binstring" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec 0.7.4", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "boring" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8259fc1ea91894a550190683fbcda307d1ef85f2875d93a2b1ab3cab58b62fea" +dependencies = [ + "bitflags 2.5.0", + "boring-sys", + "foreign-types", + "libc", + "once_cell", +] + +[[package]] +name = "boring-sys" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace69f2e0d89d2c5e0874efe47f46259ff794fe8cbddfca72132c817611ad471" +dependencies = [ + "bindgen", + "cmake", + "fs_extra", + "fslock", +] + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cached" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b0116662497bc24e4b177c90eaf8870e39e2714c3fcfa296327a93f593fc21" +dependencies = [ + "ahash", + "cached_proc_macro", + "cached_proc_macro_types", + "hashbrown", + "instant", + "once_cell", + "thiserror", +] + +[[package]] +name = "cached" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8466736fe5dbcaf8b8ee24f9bbefe43c884dc3e9ff7178da70f55bffca1133c" +dependencies = [ + "ahash", + "hashbrown", + "instant", + "once_cell", + "thiserror", +] + +[[package]] +name = "cached_proc_macro" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c878c71c2821aa2058722038a59a67583a4240524687c6028571c9b395ded61f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + +[[package]] +name = "candid" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "088c2e3d22a0fb1ada78b968946b0f7b96027ac8669973fe7c0815a98e8d13ef" +dependencies = [ + "anyhow", + "binread", + "byteorder", + "candid_derive", + "hex", + "ic_principal", + "leb128", + "num-bigint", + "num-traits", + "paste", + "pretty", + "serde", + "serde_bytes", + "stacker", + "thiserror", +] + +[[package]] +name = "candid_derive" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3de398570c386726e7a59d9887b68763c481477f9a043fb998a2e09d428df1a9" +dependencies = [ + "lazy_static", + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "candid_parser" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48a3da76f989cd350b7342c64c6c6008341bb6186f6832ef04e56dc50ba0fd76" +dependencies = [ + "anyhow", + "candid", + "codespan-reporting", + "convert_case", + "hex", + "lalrpop", + "lalrpop-util", + "logos", + "num-bigint", + "pretty", + "thiserror", +] + +[[package]] +name = "canister_sig_util" +version = "0.1.0" +source = "git+https://github.com/dfinity/internet-identity?tag=release-2024-08-21#6e0f8574125554f69c23c487b2301c7b7b5f85b3" +dependencies = [ + "candid", + "hex", + "ic-cdk 0.13.5", + "ic-certification", + "ic-representation-independent-hash", + "lazy_static", + "serde", + "serde_bytes", + "serde_cbor", + "sha2 0.10.8", +] + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", +] + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "coarsetime" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519-compact" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +dependencies = [ + "ct-codecs", + "getrandom", +] + +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core", + "serde", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-sha1-compact" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9d405ec732fa3fcde87264e54a32a84956a377b3e3107de96e59b798c84a7" + +[[package]] +name = "hmac-sha256" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "ic-agent" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd3fdf5e5c4f4a9fe5ca612f0febd22dcb161d2f2b75b0142326732be5e4978" +dependencies = [ + "async-lock", + "backoff", + "cached 0.52.0", + "candid", + "ed25519-consensus", + "futures-util", + "hex", + "http", + "http-body", + "ic-certification", + "ic-transport-types", + "ic-verify-bls-signature", + "k256", + "leb128", + "p256", + "pem", + "pkcs8", + "rand", + "rangemap", + "reqwest", + "ring", + "rustls-webpki", + "sec1", + "serde", + "serde_bytes", + "serde_cbor", + "serde_repr", + "sha2 0.10.8", + "simple_asn1", + "thiserror", + "time", + "tokio", + "url", +] + +[[package]] +name = "ic-cbor" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b0e48b4166c891e79d624f3a184b4a7c145d307576872d9a46dedb8c73ea8f" +dependencies = [ + "candid", + "ic-certification", + "leb128", + "nom", + "thiserror", +] + +[[package]] +name = "ic-cdk" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b1da6a25b045f9da3c9459c0cb2b0700ac368ee16382975a17185a23b9c18ab" +dependencies = [ + "candid", + "ic-cdk-macros 0.13.2", + "ic0 0.21.1", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-cdk" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038ff230bf0fc8630943e3c52e989d248a7c89834ccb65da408fabc5817a475b" +dependencies = [ + "candid", + "ic-cdk-macros 0.15.0", + "ic0 0.23.0", + "serde", + "serde_bytes", +] + +[[package]] +name = "ic-cdk-macros" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45800053d80a6df839a71aaea5797e723188c0b992618208ca3b941350c7355" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream 0.1.7", + "syn 1.0.109", +] + +[[package]] +name = "ic-cdk-macros" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af44fb4ec3a4b18831c9d3303ca8fa2ace846c4022d50cb8df4122635d3782e" +dependencies = [ + "candid", + "proc-macro2", + "quote", + "serde", + "serde_tokenstream 0.2.2", + "syn 2.0.55", +] + +[[package]] +name = "ic-cdk-timers" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61fdca8e1d9ffb65ae68019b342c182009de9dc206fd849db0b0e90ee19f6fa4" +dependencies = [ + "futures", + "ic-cdk 0.15.1", + "ic0 0.23.0", + "serde", + "serde_bytes", + "slotmap", +] + +[[package]] +name = "ic-certificate-verification" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "586e09b06a93d930f6a33f5f909bb11d2e4a06be3635dd5da1eb0e6554b7dae4" +dependencies = [ + "cached 0.47.0", + "candid", + "ic-cbor", + "ic-certification", + "lazy_static", + "leb128", + "miracl_core_bls12381", + "nom", + "parking_lot", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "ic-certification" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64ee3d8b6e81b51f245716d3e0badb63c283c00f3c9fb5d5219afc30b5bf821" +dependencies = [ + "hex", + "serde", + "serde_bytes", + "sha2 0.10.8", +] + +[[package]] +name = "ic-representation-independent-hash" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ae59483e377cd9aad94ec339ed1d2583b0d5929cab989328dac2d853b2f570" +dependencies = [ + "leb128", + "sha2 0.10.8", +] + +[[package]] +name = "ic-stable-structures" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a314297eb9edb4bbcc2e04d2e634e38d5900b68eadae661e927946d1aba3f9f7" +dependencies = [ + "ic_principal", +] + +[[package]] +name = "ic-transport-types" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875dc4704780383112e8e8b5063a1b98de114321d0c7d3e7f635dcf360a57fba" +dependencies = [ + "candid", + "hex", + "ic-certification", + "leb128", + "serde", + "serde_bytes", + "serde_repr", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "ic-verify-bls-signature" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d420b25c0091059f6c3c23a21427a81915e6e0aca3b79e0d403ed767f286a3b9" +dependencies = [ + "hex", + "ic_bls12_381", + "lazy_static", + "pairing", + "rand", + "sha2 0.10.8", +] + +[[package]] +name = "ic0" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a54b5297861c651551676e8c43df805dad175cc33bc97dbd992edbbb85dcbcdf" + +[[package]] +name = "ic0" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de254dd67bbd58073e23dc1c8553ba12fa1dc610a19de94ad2bbcd0460c067f" + +[[package]] +name = "ic_bls12_381" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22c65787944f32af084dffd0c68c1e544237b76e215654ddea8cd9f527dd8b69" +dependencies = [ + "digest 0.10.7", + "ff", + "group", + "pairing", + "rand_core", + "subtle", +] + +[[package]] +name = "ic_principal" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1762deb6f7c8d8c2bdee4b6c5a47b60195b74e9b5280faa5ba29692f8e17429c" +dependencies = [ + "arbitrary", + "crc32fast", + "data-encoding", + "serde", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken-rustcrypto" +version = "1.2.0" +source = "git+https://github.com/ilbertt/jsonwebtoken#9f0d34dbaff69faf18d6bd4515ec04545ddafee4" +dependencies = [ + "base64 0.22.1", + "hmac", + "rand", + "rsa", + "serde", + "serde_json", + "serde_plain", + "sha2 0.10.8", +] + +[[package]] +name = "jwt-simple" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094661f5aad510abe2658bff20409e89046b753d9dc2d4007f5c100b6d982ba0" +dependencies = [ + "anyhow", + "binstring", + "blake2b_simd", + "boring", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand", + "serde", + "serde_json", + "superboring", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.8", + "signature", +] + +[[package]] +name = "lalrpop" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax 0.8.3", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.6", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.5.0", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "logos" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 2.0.55", +] + +[[package]] +name = "logos-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "miracl_core_bls12381" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07cbe42e2a8dd41df582fb8e00fc24d920b5561cc301fcb6d14e2e0434b500f" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "serde", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2 0.10.8", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pocket-ic" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629f46b7ab8a8d2fee02220ef8e99ae552c7e220117efa1ce0882ff09c8fb038" +dependencies = [ + "base64 0.13.1", + "candid", + "hex", + "ic-cdk 0.13.5", + "reqwest", + "schemars", + "serde", + "serde_bytes", + "serde_json", + "sha2 0.10.8", + "tokio", + "tracing", + "tracing-appender", + "tracing-subscriber", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "pretty" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55c4d17d994b637e2f4daf6e5dc5d660d209d5642377d675d7a1c3ab69fa579" +dependencies = [ + "arrayvec 0.5.2", + "typed-arena", + "unicode-width", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "quinn" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.0.0", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash 2.0.0", + "rustls", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rangemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.3", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2 0.10.8", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.55", + "unicode-ident", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04182dffc9091a404e0fc069ea5cd60e5b866c3adf881eff99a32d048242dffa" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "serde_tokenstream" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "797ba1d80299b264f3aac68ab5d12e5825a561749db4df7cd7c8083900c5d4e9" +dependencies = [ + "proc-macro2", + "serde", + "syn 1.0.109", +] + +[[package]] +name = "serde_tokenstream" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.55", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "ssp_backend" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "candid", + "candid_parser", + "canister_sig_util", + "chrono", + "getrandom", + "hex", + "hex-literal", + "ic-agent", + "ic-cdk 0.15.1", + "ic-cdk-timers", + "ic-certificate-verification", + "ic-certification", + "ic-representation-independent-hash", + "ic-stable-structures", + "jsonwebtoken-rustcrypto", + "jwt-simple", + "pocket-ic", + "rand", + "rand_chacha", + "ring", + "rstest", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", + "sha2 0.10.8", + "ssp_backend_types", + "uuid", +] + +[[package]] +name = "ssp_backend_types" +version = "0.1.0" +dependencies = [ + "candid", + "serde", + "serde_bytes", + "serde_cbor", + "serde_json", +] + +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + +[[package]] +name = "superboring" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbde97f499e51ef384f585dc8f8fb6a9c3a71b274b8d12469b516758e6540607" +dependencies = [ + "getrandom", + "hmac-sha256", + "hmac-sha512", + "rand", + "rsa", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.39.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" +dependencies = [ + "either", + "futures-util", + "thiserror", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" +dependencies = [ + "crossbeam-channel", + "thiserror", + "time", + "tracing-subscriber", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.55", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.55", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2df58c4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[workspace] +members = ["apps/ssp_backend", "packages/ssp_backend_types"] +resolver = "2" + +[workspace.package] +edition = "2021" + +[workspace.dependencies] +candid = "0.10" +ic-certification = "2.6" + +serde = "1.0" +serde_bytes = "0.11" +serde_json = { version = "1.0", features = ["preserve_order"] } +serde_cbor = "0.11" + +rstest = "0.18" + +ssp_backend_types = { path = "packages/ssp_backend_types" } diff --git a/README.md b/README.md new file mode 100644 index 0000000..75ef62d --- /dev/null +++ b/README.md @@ -0,0 +1,294 @@ +# XGS platform monorepo + +This [Turborepo](https://turbo.build/repo) contains the source code for the XGS platform. + +## Requirements + +- [Node.js](https://nodejs.org/) +- [pnpm](https://pnpm.io/) package manager +- [Expo's requirements](https://docs.expo.dev/get-started/installation/#requirements) and [local development prerequisites](https://docs.expo.dev/guides/local-app-development/#prerequisites) +- [Rust](https://www.rust-lang.org/) with the `wasm32-unknown-unknown` target: + + ```bash + rustup target add wasm32-unknown-unknown + ``` + +- [dfx](https://internetcomputer.org/docs/current/developer-docs/getting-started/install/) (better if installed with the dfx version manager - [`dfxvm`](https://github.com/dfinity/dfxvm)) +- an [Auth0](https://auth0.com) account +- an Android/iOS device or simulator + +## Setup + +### Configure Auth0 + +Follow these steps to configure Auth0 for this project: + +1. [Create a Tenant](https://auth0.com/docs/get-started/auth0-overview/create-tenants) and get your Auth0 Tenant domain, which looks like `..auth0.com` + +#### Create a Native Application + +The Auth0 Native Application is used to authenticated users from the [Mobile app](#mobile-app). Follow these steps: + +1. [Create a Native Application](https://auth0.com/docs/get-started/auth0-overview/create-applications/native-apps) +2. In the _Dashboard > Applications > YOUR_APP > Settings_ tab, set the **Allowed Callback URLs** and **Allowed Logout URLs** to: + + - `io.icp0.jwtauthdemo.auth0:///ios/io.icp0.jwtauthdemo/callback` + - `io.icp0.jwtauthdemo.auth0:///android/io.icp0.jwtauthdemo/callback` + + Where `` is the Auth0 Tenant domain and `io.icp0.jwtauthdemo` is both the **Android Package Name** and **iOS Bundle Identifier**, as configured in the [app.config.js](./src/app/app.config.js) file. + +3. In the _Dashboard > Applications > YOUR_APP > Credentials_ tab, set the **Authentication Method** to **None** (instead of **Client Secret (Post)**) + +The 1st step of the Auth0 React Native [Quickstart interactive guide](https://auth0.com/docs/quickstart/native/react-native-expo/interactive) can be helpful too. + +#### Create a Web Application + +The Auth0 Web Application is used to authenticated users from the [Web app](#web-app). Follow these steps: + +1. [Create a Web Application](https://auth0.com/docs/get-started/auth0-overview/create-applications/web-apps) +2. In the _Dashboard > Applications > YOUR_APP > Settings_ tab: + + - set the **Allowed Callback URLs** to `http://localhost:3000/api/auth/callback` + - set the **Allowed Logout URLs** to `http://localhost:3000` + + When you'll deploy the web app on a public domain, you will have to adjust these URLs accordingly. + +3. Configure the **Login Flow** in order to add custom JWT claims to the token minted by Auth0 and sync the user with Hasura. See [this guide](https://hasura.io/learn/graphql/hasura-authentication/integrations/auth0/#customjwtclaims) for more details. + Write the following **Login / Post Login Custom Action** (select the _Node.js 18_ runtime): + + **Hasura Sync User and JWT Claims** + + ```javascript + const fetch = require("node-fetch"); + + const HASURA_GRAPHQL_URL = + "https://alive-glowworm-caring.ngrok-free.app//v1/graphql"; + + /** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ + exports.onExecutePostLogin = async (event, api) => { + const auth0Id = event.user.user_id; + const email = event.user.email ?? null; + const lastLoginAt = event.session?.authenticated_at ?? new Date(); + + const admin_secret = event.secrets.HASURA_GRAPHQL_ADMIN_SECRET; + + const query = `mutation UpsertUser($auth0Id: String!, $email: citext!, $lastLoginAt: timestamptz!, $isPending: Boolean!) { + insert_users_one( + object: {auth0_id: $auth0Id, email: $email, last_login_at: $lastLoginAt, is_pending: $isPending}, + on_conflict: {constraint: users_email_key, update_columns: [auth0_id, is_pending, last_login_at]} + ) { + id + } + } + `; + + const variables = { auth0Id, email, lastLoginAt, isPending: false }; + + const res = await fetch(HASURA_GRAPHQL_URL, { + method: "POST", + body: JSON.stringify({ + query: query, + variables: variables, + }), + headers: { + "content-type": "application/json", + "x-hasura-admin-secret": admin_secret, + }, + }); + + const graphqlRes = await res.json(); + + console.log("GraphQL Response:", graphqlRes); + + if (graphqlRes.errors && graphqlRes.errors.length > 0) { + const err = graphqlRes.errors[0]; + api.access.deny(err?.message || "GraphQL API returned an error."); + return; + } + + if (!graphqlRes.data) { + api.access.deny("No data from GraphQL API."); + return; + } + + if (event.authorization) { + const userId = graphqlRes.data.insert_users_one.id; + const roles = ["user"]; + const customClaimKey = "https://hasura.io/jwt/claims"; + const customClaimValue = { + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": roles, + "x-hasura-user-id": userId, + }; + + api.idToken.setCustomClaim(customClaimKey, customClaimValue); + api.accessToken.setCustomClaim(customClaimKey, customClaimValue); + } + }; + ``` + + Finally, add this action to the Login Flow in the Auth0 Dashboard. + + In order for the Auth0 Login Action to access the local Hasura API, you need to run a tunnel like ngrok. See the [backend's README tunnel](./apps/backend/README.md#tunnel) for more details. + +The Next.js Quickstart [guide](https://auth0.com/docs/quickstart/webapp/nextjs) can be helpful too. + +#### Test users + +Create the following test users in the Auth0 Dashboard: + +| Username | Password | +| --------------------------- | -------------- | +| `testmanagerlugano@test.io` | `Thisisatest!` | +| `testparent@test.io` | `Thisisatest!` | + +### Install dependencies + +To install the dependencies, run: + +```bash +pnpm install +``` + +This will install the dependencies for all the apps and packages in the monorepo. + +### Configure environment variables + +In every app, there's a `.env.example` file that you can use as reference to create the relative `.env` file. + +## Usage + +### SwissSportPass backend canister + +The backend canister is located in the [`apps/ssp_backend`](./apps/ssp_backend/) folder. + +To deploy the backend canister on the local dfx replica, run the following commands: + +1. Start the local replica **from the `apps/ssp_backend` folder**: + + ```bash + pnpm dev + ``` + + Keep this terminal open. + +2. In another terminal, deploy the `ssp_backend` canister **from the root of the monorepo**: + + ```bash + ENV_FILE_PATH=apps/ssp_backend/.env && pnpm run deploy --filter=ssp_backend + ``` + +### Mobile app + +The mobile app is located in the [`apps/mobile`](./apps/mobile/) folder, and is built with [Expo](https://expo.dev/). + +#### Run the mobile app in dev mode + +To start the mobile app in dev mode, run the following commands **from the `apps/mobile` folder**: + +1. Make sure you've deployed the backend canister, see the [SwissSportPass backend canister](#swisssportpass-backend-canister) section. This generates a `.env` file in the [`packages/config`](./packages/config/) folder with the necessary environment variables to connect to the backend canister from the mobile app. + +2. [Prebuild](https://docs.expo.dev/workflow/prebuild/) the mobile app: + + ```bash + pnpm expo prebuild + ``` + + > Note: you should only do this the first time you build the mobile app. + +3. Start the Expo development server: + + ```bash + pnpm dev + ``` + + More info on how to use Expo dev server on their docs: https://docs.expo.dev/more/expo-cli/#develop. + +### Web app + +The web app is located in the [`apps/web`](./apps/web/) folder, and is build with [Next.js](https://nextjs.org/). + +#### Run the web app in dev mode + +To start the web app in dev mode, run the following command **from the `apps/web` folder**: + +```bash +pnpm dev +``` + +#### Build the web app + +To build the web app, run the following command **from the `apps/web` folder**: + +```bash +pnpm build +``` + +### Off-chain Backend + +Head over to the off-chain backend's [README](./apps/backend/README.md) for more information. + +## Seed data + +Seed data is located in the [`apps/backend/hasura/seeds`](./apps/backend/hasura/seeds) folder. To apply the seed data, run: + +```bash +pnpm hasura:seed-apply +``` + +## Tests + +Run the tests in all packages with: + +```bash +pnpm test +``` + +This script uses Turborepo's dependencies to run the `pretest` script in all packages before running the tests. Use the `pretest` script to prepare the environment before running the tests (e.g. building the binaries, etc.). + +If you want to skip Turborepo's cache, run: + +```bash +pnpm test -- --force +``` + +## Linting + +Run the linters in all packages with: + +```bash +pnpm lint +``` + +## Formatting + +Run the formatters in all packages with: + +```bash +pnpm format +``` + +## Turborepo and Rust + +Turborepo doesn't officially support Rust yet, but we have a workaround to make it work with the cache system of Turborepo. + +When you want to add a Rust **app** to the monorepo: + +1. Add the Rust app in the `apps` folder +2. Add a `package.json` file with the `` into the app folder +3. Add any script needed for the app to build, lint, etc. in the `package.json` file + +When you want to add a Rust **package** to the monorepo: + +1. Add the Rust package in the `packages` folder +2. Add a `package.json` file with the `` name into the package folder +3. Add any script needed for the package to build, lint, etc. in the `package.json` file +4. Import the Rust package in the app's `Cargo.toml` file where needed +5. Import the workspace package in the package.json of the app, so that Turbo recognizes it as a dependency and handles cache properly + +See [`apps/ssp_backend`](./apps/ssp_backend/) and [`packages/ssp_backend_types`](./packages/ssp_backend_types/) for reference. diff --git a/apps/auth0/.env.example b/apps/auth0/.env.example new file mode 100644 index 0000000..c95c82b --- /dev/null +++ b/apps/auth0/.env.example @@ -0,0 +1,8 @@ +AUTH0_DOMAIN=xgensport-dev.eu.auth0.com +AUTH0_CLIENT_ID=1ECmNx3r5iXg75DZiYqqrGeC4IRwkXEz +AUTH0_CLIENT_SECRET=... +AUTH0_PRESERVE_KEYWORDS=true +AUTH0_ALLOW_DELETE=true +WEB_APP_URL=http://localhost:3000 +HASURA_GRAPHQL_ENDPOINT=https://alive-glowworm-caring.ngrok-free.app +HASURA_GRAPHQL_ADMIN_SECRET=secret-key diff --git a/apps/auth0/config/actions/Hasura User Sync and JWT Claims/code.js b/apps/auth0/config/actions/Hasura User Sync and JWT Claims/code.js new file mode 100644 index 0000000..cdbe592 --- /dev/null +++ b/apps/auth0/config/actions/Hasura User Sync and JWT Claims/code.js @@ -0,0 +1,42 @@ +const fetch = require("node-fetch"); + +/** + * Handler that will be called during the execution of a PostLogin flow. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +exports.onExecutePostLogin = async (event, api) => { + const auth0Id = event.user.user_id; + const email = event.user.email ?? null; + const lastLoginAt = event.session?.authenticated_at ?? new Date(); + + const graphql_endpoint = event.secrets.HASURA_GRAPHQL_ENDPOINT; + const admin_secret = event.secrets.HASURA_GRAPHQL_ADMIN_SECRET; + + // Sync your user here + + if (event.authorization) { + const userId = ""; + const roles = ["user"]; + const customClaimKey = "https://hasura.io/jwt/claims"; + const customClaimValue = { + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": roles, + "x-hasura-user-id": userId, + }; + + api.idToken.setCustomClaim(customClaimKey, customClaimValue); + api.accessToken.setCustomClaim(customClaimKey, customClaimValue); + } +}; + +/** + * Handler that will be invoked when this action is resuming after an external redirect. If your + * onExecutePostLogin function does not perform a redirect, this function can be safely ignored. + * + * @param {Event} event - Details about the user and the context in which they are logging in. + * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. + */ +// exports.onContinuePostLogin = async (event, api) => { +// }; diff --git a/apps/auth0/config/tenant.yaml b/apps/auth0/config/tenant.yaml new file mode 100644 index 0000000..36a7d66 --- /dev/null +++ b/apps/auth0/config/tenant.yaml @@ -0,0 +1,197 @@ +rules: [] +rulesConfigs: [] +pages: [] +resourceServers: + - name: mobile + identifier: mobile + allow_offline_access: true + enforce_policies: true + signing_alg: RS256 + skip_consent_for_verifiable_first_party_clients: true + token_dialect: access_token_authz + token_lifetime: 86400 + token_lifetime_for_web: 7200 +clients: + - name: Native App + allowed_clients: [] + allowed_logout_urls: '@@NATIVE_APP_LOGOUT_URLS@@' + app_type: native + callbacks: '@@NATIVE_APP_CALLBACK_URLS@@' + client_aliases: [] + cross_origin_auth: false + custom_login_page_on: true + grant_types: + - authorization_code + - implicit + - refresh_token + is_first_party: true + is_token_endpoint_ip_header_trusted: false + jwt_configuration: + alg: RS256 + lifetime_in_seconds: 36000 + secret_encoded: false + native_social_login: + apple: + enabled: false + facebook: + enabled: false + oidc_conformant: true + organization_require_behavior: no_prompt + refresh_token: + expiration_type: non-expiring + leeway: 0 + infinite_token_lifetime: true + infinite_idle_token_lifetime: true + token_lifetime: 2592000 + idle_token_lifetime: 1296000 + rotation_type: non-rotating + sso_disabled: false + token_endpoint_auth_method: none + - name: deploy-cli + app_type: non_interactive + cross_origin_auth: false + custom_login_page_on: true + grant_types: + - client_credentials + is_first_party: true + is_token_endpoint_ip_header_trusted: false + jwt_configuration: + alg: RS256 + lifetime_in_seconds: 36000 + secret_encoded: false + oidc_conformant: true + refresh_token: + expiration_type: non-expiring + leeway: 0 + infinite_token_lifetime: true + infinite_idle_token_lifetime: true + token_lifetime: 31557600 + idle_token_lifetime: 2592000 + rotation_type: non-rotating + sso_disabled: false + token_endpoint_auth_method: client_secret_post +databases: + - name: Username-Password-Authentication + strategy: auth0 + enabled_clients: + - deploy-cli + - Native App + is_domain_connection: false + options: + mfa: + active: true + return_enroll_settings: true + passwordPolicy: good + passkey_options: + challenge_ui: both + local_enrollment_enabled: true + progressive_enrollment_enabled: true + strategy_version: 2 + authentication_methods: + passkey: + enabled: false + password: + enabled: true + brute_force_protection: true + realms: + - Username-Password-Authentication +connections: [] +tenant: + default_audience: '' + default_directory: '' + enabled_locales: + - en + flags: + revoke_refresh_token_grant: false + disable_clickjack_protection_headers: false + oidc_logout: + rp_logout_end_session_endpoint_discovery: true + sandbox_version: '18' +emailProvider: {} +emailTemplates: [] +clientGrants: [] +guardianFactors: + - name: duo + enabled: false + - name: email + enabled: false + - name: otp + enabled: false + - name: push-notification + enabled: false + - name: recovery-code + enabled: false + - name: sms + enabled: false + - name: webauthn-platform + enabled: false + - name: webauthn-roaming + enabled: false +guardianFactorProviders: [] +guardianFactorTemplates: [] +guardianPolicies: + policies: [] +guardianPhoneFactorSelectedProvider: + provider: auth0 +guardianPhoneFactorMessageTypes: + message_types: [] +roles: [] +branding: + templates: [] +prompts: + customText: {} + partials: {} + universal_login_experience: new +migrations: {} +actions: + - name: Hasura User Sync and JWT Claims + code: ./actions/Hasura User Sync and JWT Claims/code.js + dependencies: + - name: node-fetch + version: 2.7.0 + deployed: true + runtime: node18-actions + secrets: + - name: HASURA_GRAPHQL_ENDPOINT + - name: HASURA_GRAPHQL_ADMIN_SECRET + status: built + supported_triggers: + - id: post-login + version: v3 +triggers: + post-login: + - action_name: Hasura User Sync and JWT Claims + display_name: Hasura User Sync and JWT Claims +organizations: [] +attackProtection: + breachedPasswordDetection: + enabled: false + shields: [] + admin_notification_frequency: [] + method: standard + stage: + pre-user-registration: + shields: [] + bruteForceProtection: + enabled: true + shields: + - block + - user_notification + mode: count_per_identifier_and_ip + allowlist: [] + max_attempts: 10 + suspiciousIpThrottling: + enabled: true + shields: + - admin_notification + - block + allowlist: [] + stage: + pre-login: + max_attempts: 100 + rate: 864000 + pre-user-registration: + max_attempts: 50 + rate: 1200 +logStreams: [] +themes: [] diff --git a/apps/auth0/package.json b/apps/auth0/package.json new file mode 100644 index 0000000..e64775d --- /dev/null +++ b/apps/auth0/package.json @@ -0,0 +1,14 @@ +{ + "name": "auth0", + "version": "0.0.0", + "private": true, + "scripts": { + "deploy": "pnpm auth0:import", + "auth0:export": "dotenv -- ./scripts/auth0-cli.sh --export", + "auth0:import": "dotenv -- ./scripts/auth0-cli.sh --import" + }, + "devDependencies": { + "auth0-deploy-cli": "^7.24.1", + "dotenv-cli": "^7.4.2" + } +} diff --git a/apps/auth0/scripts/auth0-cli.sh b/apps/auth0/scripts/auth0-cli.sh new file mode 100755 index 0000000..5f8c757 --- /dev/null +++ b/apps/auth0/scripts/auth0-cli.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +IOS_NATIVE_APP_CALLBACK_URL=com.xgensport.mobile.auth0://$AUTH0_DOMAIN/ios/com.xgensport.mobile/callback +ANDROID_NATIVE_APP_CALLBACK_URL=com.xgensport.mobile.auth0://$AUTH0_DOMAIN/android/com.xgensport.mobile/callback + +export AUTH0_KEYWORD_REPLACE_MAPPINGS="{ + \"NATIVE_APP_LOGOUT_URLS\":[ + \"$IOS_NATIVE_APP_CALLBACK_URL\", + \"$ANDROID_NATIVE_APP_CALLBACK_URL\" + ], + \"NATIVE_APP_CALLBACK_URLS\":[ + \"$IOS_NATIVE_APP_CALLBACK_URL\", + \"$ANDROID_NATIVE_APP_CALLBACK_URL\" + ], + \"WEB_APP_LOGOUT_URLS\":[ + \"$WEB_APP_URL\" + ], + \"WEB_APP_CALLBACK_URLS\":[ + \"$WEB_APP_URL/api/auth/callback\" + ], + \"HASURA_GRAPHQL_ENDPOINT\":\"$HASURA_GRAPHQL_ENDPOINT/v1/graphql\", + \"HASURA_GRAPHQL_ADMIN_SECRET\":\"$HASURA_GRAPHQL_ADMIN_SECRET\" +}" + +echo -e "AUTH0_KEYWORD_REPLACE_MAPPINGS: $AUTH0_KEYWORD_REPLACE_MAPPINGS\n" + +while [[ $# -gt 0 ]]; do + case $1 in + --import) + pnpm a0deploy import --format=yaml --input_file=config/tenant.yaml + exit 0 + ;; + --export) + pnpm a0deploy export --format=yaml --output_folder=config + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + esac +done diff --git a/apps/ssp_backend/.env.example b/apps/ssp_backend/.env.example new file mode 100644 index 0000000..52e3675 --- /dev/null +++ b/apps/ssp_backend/.env.example @@ -0,0 +1,3 @@ +# env variables needed by the ssp_backend canister at build time (WITH trailing slash) +ID_TOKEN_ISSUER_BASE_URL=https:/// +ID_TOKEN_AUDIENCE= diff --git a/apps/ssp_backend/Cargo.toml b/apps/ssp_backend/Cargo.toml new file mode 100644 index 0000000..4ca4b2f --- /dev/null +++ b/apps/ssp_backend/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "ssp_backend" +version = "0.1.0" +edition.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib"] + +[dependencies] +candid.workspace = true +ic-cdk = "0.15" +ic-cdk-timers = "0.9" +ic-stable-structures = "0.6" +ic-certification.workspace = true +canister_sig_util = { git = "https://github.com/dfinity/internet-identity", tag = "release-2024-08-21" } + +serde.workspace = true +serde_json.workspace = true +serde_bytes.workspace = true +serde_cbor.workspace = true + +jsonwebtoken-rustcrypto = { git = "https://github.com/ilbertt/jsonwebtoken" } +hex = "0.4" +getrandom = { version = "0.2", features = ["custom"] } +base64 = "0.22" +sha2 = "0.10" +uuid = { version = "1.10", features = ["serde"] } +chrono = { version = "0.4", default-features = false, features = ["std"] } +rand = { version = "0.8", default-features = false } +rand_chacha = { version = "0.3", default-features = false } + +ssp_backend_types.workspace = true + +[dev-dependencies] +hex-literal = "0.4" +pocket-ic = "4.0" +jwt-simple = "0.12" +ic-agent = "0.37" +ring = "0.17" +ic-representation-independent-hash = "2.6" +candid_parser = "0.1" +rstest.workspace = true +ic-certificate-verification = "2.6" diff --git a/apps/ssp_backend/package.json b/apps/ssp_backend/package.json new file mode 100644 index 0000000..577f416 --- /dev/null +++ b/apps/ssp_backend/package.json @@ -0,0 +1,17 @@ +{ + "name": "ssp_backend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "dfx start --clean --host 0.0.0.0:4943", + "lint": "ID_TOKEN_ISSUER_BASE_URL=https://dummy-issuer-url.local/ ID_TOKEN_AUDIENCE=dummy-audience cargo clippy --all-targets --all-features -- -D warnings", + "pretest": "ID_TOKEN_ISSUER_BASE_URL=http://integration-test.local/ ID_TOKEN_AUDIENCE=integration-test-audience ./scripts/pretest.sh", + "test:unit": "ID_TOKEN_ISSUER_BASE_URL=http://unit-test.local/ ID_TOKEN_AUDIENCE=unit-test-audience cargo test --package ssp_backend --lib", + "test:integration": "ID_TOKEN_ISSUER_BASE_URL=http://integration-test.local/ ID_TOKEN_AUDIENCE=integration-test-audience ./scripts/integration-test.sh", + "test": "pnpm test:unit && pnpm test:integration", + "deploy": "cd ../.. && dfx deploy --network=\"${DFX_NETWORK:-local}\" --yes ssp_backend" + }, + "dependencies": { + "ssp_backend_types": "workspace:*" + } +} diff --git a/apps/ssp_backend/scripts/build-canister.sh b/apps/ssp_backend/scripts/build-canister.sh new file mode 100755 index 0000000..1d7dfab --- /dev/null +++ b/apps/ssp_backend/scripts/build-canister.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +set -e + +# parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --issuer) + ID_TOKEN_ISSUER_BASE_URL="$2" + shift # past argument + shift # past value + ;; + --audience) + ID_TOKEN_AUDIENCE="$2" + shift # past argument + shift # past value + ;; + --env-file) + ENV_FILE_PATH="$2" + shift # past argument + shift # past value + ;; + --ignore-env-file) + ENV_FILE_PATH="" + shift # past argument + ;; + esac +done + +# load environment variables from .env +if [[ ! -z "$ENV_FILE_PATH" ]]; then + echo -e "\nLoading environment variables from $ENV_FILE_PATH file...\n" + source $ENV_FILE_PATH +fi + +if [[ -z "$ID_TOKEN_ISSUER_BASE_URL" ]]; then + echo -e "\nError: ID_TOKEN_ISSUER_BASE_URL is not set\n" + exit 1 +fi + +if [[ -z "$ID_TOKEN_AUDIENCE" ]]; then + echo -e "\nError: ID_TOKEN_AUDIENCE is not set\n" + exit 1 +fi + +# build canister +echo -e "\nBuilding canister..." +echo -e "JWT Issuer: $ID_TOKEN_ISSUER_BASE_URL\nJWT Audience: $ID_TOKEN_AUDIENCE\n" + +ID_TOKEN_ISSUER_BASE_URL=$ID_TOKEN_ISSUER_BASE_URL \ +ID_TOKEN_AUDIENCE=$ID_TOKEN_AUDIENCE \ +cargo build --target wasm32-unknown-unknown --release -p ssp_backend --locked + +echo -e "\nDone!\n" diff --git a/apps/ssp_backend/scripts/download-pocket-ic.sh b/apps/ssp_backend/scripts/download-pocket-ic.sh new file mode 100755 index 0000000..6d3d735 --- /dev/null +++ b/apps/ssp_backend/scripts/download-pocket-ic.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +set -e + +mkdir -p bin + +cd bin/ + +POCKET_IC_BIN=pocket-ic +if [ -f "$POCKET_IC_BIN" ]; then + echo -e "$POCKET_IC_BIN exists. Path: $(pwd)/$POCKET_IC_BIN\n" +else + echo "$POCKET_IC_BIN does not exist." + + echo "Downloading Pocket IC binary..." + if [[ "$OSTYPE" == "linux"* ]]; then + ARCH="linux" + elif [[ "$OSTYPE" == "darwin"* ]]; then + ARCH="darwin" + else + echo "Unsupported OS: $OSTYPE" + exit 1 + fi + curl -sL -o $POCKET_IC_BIN.gz https://github.com/dfinity/pocketic/releases/download/5.0.0/pocket-ic-x86_64-$ARCH.gz + + echo "Extracting Pocket IC binary..." + gzip -d $POCKET_IC_BIN.gz + chmod +x $POCKET_IC_BIN + + echo -e "Pocket IC binary downloaded and extracted successfully! Path: $(pwd)/$POCKET_IC_BIN\n" +fi diff --git a/apps/ssp_backend/scripts/integration-test.sh b/apps/ssp_backend/scripts/integration-test.sh new file mode 100755 index 0000000..bfd0338 --- /dev/null +++ b/apps/ssp_backend/scripts/integration-test.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +BIN_DIR="$(pwd)/bin" + +ID_TOKEN_ISSUER_BASE_URL=$ID_TOKEN_ISSUER_BASE_URL \ +ID_TOKEN_AUDIENCE=$ID_TOKEN_AUDIENCE \ +POCKET_IC_MUTE_SERVER=1 \ +POCKET_IC_BIN="$BIN_DIR/pocket-ic" \ +TEST_CANISTER_WASM_PATH="../../target/wasm32-unknown-unknown/release/ssp_backend.wasm" \ +cargo test --package ssp_backend --test '*' diff --git a/apps/ssp_backend/scripts/pretest.sh b/apps/ssp_backend/scripts/pretest.sh new file mode 100755 index 0000000..3d5bf6b --- /dev/null +++ b/apps/ssp_backend/scripts/pretest.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e + +./scripts/download-pocket-ic.sh + +./scripts/build-canister.sh --ignore-env-file --issuer $ID_TOKEN_ISSUER_BASE_URL --audience $ID_TOKEN_AUDIENCE diff --git a/apps/ssp_backend/src/controllers/certificate_controller.rs b/apps/ssp_backend/src/controllers/certificate_controller.rs new file mode 100644 index 0000000..a0c4e77 --- /dev/null +++ b/apps/ssp_backend/src/controllers/certificate_controller.rs @@ -0,0 +1,82 @@ +use candid::Principal; +use ic_cdk::{caller, query, update}; +use ssp_backend_types::{ + CreateCertificateRequest, CreateCertificateResponse, GetCertificateResponse, + GetUserCertificatesRequest, GetUserCertificatesResponse, +}; + +use crate::services::{AccessControlService, CertificateService}; + +#[update] +async fn create_certificate(req: CreateCertificateRequest) -> CreateCertificateResponse { + let calling_principal = caller(); + + CertificateController::default() + .create_certificate(calling_principal, req) + .await +} + +#[query] +fn get_user_certificates(req: GetUserCertificatesRequest) -> GetUserCertificatesResponse { + let calling_principal = caller(); + + CertificateController::default().get_user_certificates(calling_principal, req) +} + +#[query] +fn get_certificate(id: String) -> GetCertificateResponse { + let calling_principal = caller(); + + CertificateController::default().get_certificate(calling_principal, id) +} + +#[derive(Default)] +struct CertificateController { + access_control_service: AccessControlService, + certificate_service: CertificateService, +} + +impl CertificateController { + async fn create_certificate( + &self, + calling_principal: Principal, + req: CreateCertificateRequest, + ) -> CreateCertificateResponse { + let calling_user_principal = self + .access_control_service + .assert_principal_is_user_or_backend(&calling_principal) + .unwrap(); + + self.certificate_service + .create_certificate(req, calling_user_principal.cloned()) + .await + .unwrap() + } + + fn get_user_certificates( + &self, + calling_principal: Principal, + req: GetUserCertificatesRequest, + ) -> GetUserCertificatesResponse { + // if the calling principal is the backend, all certificates are visible + let only_user_principal = self + .access_control_service + .assert_principal_is_user_or_backend(&calling_principal) + .unwrap(); + + self.certificate_service + .get_user_certificates(req, only_user_principal.cloned()) + .unwrap() + } + + fn get_certificate(&self, calling_principal: Principal, id: String) -> GetCertificateResponse { + let only_user_principal = self + .access_control_service + .assert_principal_is_user_or_backend(&calling_principal) + .unwrap(); + + self.certificate_service + .get_certificate(id, only_user_principal.cloned()) + .unwrap() + } +} diff --git a/apps/ssp_backend/src/controllers/config_controller.rs b/apps/ssp_backend/src/controllers/config_controller.rs new file mode 100644 index 0000000..ec6e7f1 --- /dev/null +++ b/apps/ssp_backend/src/controllers/config_controller.rs @@ -0,0 +1,44 @@ +use candid::Principal; +use ic_cdk::{caller, query, update}; + +use crate::services::{AccessControlService, ConfigService}; + +#[update] +fn set_backend_principal(principal: Principal) { + let calling_principal = caller(); + + ConfigController::default().set_backend_principal(calling_principal, principal); +} + +#[query] +fn get_config() -> ssp_backend_types::Config { + let calling_principal = caller(); + + ConfigController::default().get_config(calling_principal) +} + +#[derive(Default)] +struct ConfigController { + access_control_service: AccessControlService, + config_service: ConfigService, +} + +impl ConfigController { + fn set_backend_principal(&self, calling_principal: Principal, principal: Principal) { + self.access_control_service + .assert_principal_is_controller(&calling_principal) + .unwrap(); + + self.config_service + .set_backend_principal(principal) + .unwrap() + } + + fn get_config(&self, calling_principal: Principal) -> ssp_backend_types::Config { + self.access_control_service + .assert_principal_is_controller(&calling_principal) + .unwrap(); + + self.config_service.get_config().into() + } +} diff --git a/apps/ssp_backend/src/controllers/delegation_controller.rs b/apps/ssp_backend/src/controllers/delegation_controller.rs new file mode 100644 index 0000000..35d4662 --- /dev/null +++ b/apps/ssp_backend/src/controllers/delegation_controller.rs @@ -0,0 +1,114 @@ +use candid::Principal; +use ic_cdk::{ + api::management_canister::http_request::{HttpResponse, TransformArgs}, + caller, query, update, +}; +use ssp_backend_types::{Auth0JWKSet, GetDelegationResponse, PrepareDelegationResponse, Timestamp}; + +use crate::services::{AccessControlService, DelegationService}; + +#[update] +async fn prepare_delegation(jwt: String) -> PrepareDelegationResponse { + let calling_principal = caller(); + + DelegationController::default() + .prepare_delegation(calling_principal, jwt) + .await +} + +#[query] +fn get_delegation(jwt: String, expiration: Timestamp) -> GetDelegationResponse { + let calling_principal = caller(); + + DelegationController::default().get_delegation(calling_principal, jwt, expiration) +} + +#[update] +async fn sync_jwks() { + let calling_principal = caller(); + + DelegationController::default() + .sync_jwks(calling_principal) + .await +} + +#[query(hidden = true)] +fn transform_jwks_response(args: TransformArgs) -> HttpResponse { + DelegationController::default().transform_jwks_response(args) +} + +#[update] +// used in tests +fn set_jwks(jwks: Auth0JWKSet) { + let calling_principal = caller(); + + DelegationController::default().set_jwks(calling_principal, jwks); +} + +#[query] +// used in tests +fn get_jwks() -> Option { + let calling_principal = caller(); + + DelegationController::default().get_jwks(calling_principal) +} + +#[derive(Default)] +struct DelegationController { + access_control_service: AccessControlService, + delegation_service: DelegationService, +} + +impl DelegationController { + async fn prepare_delegation( + &self, + calling_principal: Principal, + jwt: String, + ) -> PrepareDelegationResponse { + self.delegation_service + .prepare_delegation(calling_principal, jwt) + .await + .unwrap() + } + + fn get_delegation( + &self, + calling_principal: Principal, + jwt: String, + expiration: Timestamp, + ) -> GetDelegationResponse { + self.delegation_service + .get_delegation(calling_principal, jwt, expiration) + } + + async fn sync_jwks(&self, calling_principal: Principal) { + self.access_control_service + .assert_principal_is_controller(&calling_principal) + .unwrap(); + + self.delegation_service + .fetch_and_store_jwks() + .await + .unwrap() + } + + fn set_jwks(&self, calling_principal: Principal, jwks: Auth0JWKSet) { + self.access_control_service + .assert_principal_is_controller(&calling_principal) + .unwrap(); + + self.delegation_service.set_jwks(jwks) + } + + fn get_jwks(&self, calling_principal: Principal) -> Option { + self.access_control_service + .assert_principal_is_controller(&calling_principal) + .unwrap(); + + self.delegation_service.get_jwks() + } + + fn transform_jwks_response(&self, args: TransformArgs) -> HttpResponse { + self.delegation_service.transform_jwks_response(args) + } +} diff --git a/apps/ssp_backend/src/controllers/init_controller.rs b/apps/ssp_backend/src/controllers/init_controller.rs new file mode 100644 index 0000000..2877cb4 --- /dev/null +++ b/apps/ssp_backend/src/controllers/init_controller.rs @@ -0,0 +1,81 @@ +use std::time::Duration; + +use ic_cdk::{init, post_upgrade, spawn, trap}; +use ic_cdk_timers::set_timer; + +use crate::services::{CertificateService, DelegationService}; + +#[init] +fn init() { + set_timer(Duration::ZERO, move || spawn(init_task())); + + jobs::start_jobs(); +} + +#[post_upgrade] +fn post_upgrade() { + set_timer(Duration::ZERO, move || spawn(init_task())); + + jobs::start_jobs(); +} + +async fn init_task() { + let init_controller = InitController::default(); + // We first certify all certificates, then we run the https outcalls. + // If we invert the order, this fails in the tests with PocketIC. + init_controller.certify_all_certificates(); + init_controller.init_delegation().await; +} + +#[derive(Default)] +struct InitController { + delegation_service: DelegationService, + certificate_service: CertificateService, +} + +impl InitController { + async fn init_delegation(&self) { + self.delegation_service.ensure_salt_initialized().await; + + self.fetch_jwks().await; + } + + async fn fetch_jwks(&self) { + if let Err(e) = self.delegation_service.fetch_and_store_jwks().await { + trap(&format!("failed to fetch and store jwks: {e}")); + } + } + + fn certify_all_certificates(&self) { + self.certificate_service.certify_all_certificates(); + } +} + +mod jobs { + use ic_cdk::spawn; + use ic_cdk_timers::set_timer_interval; + use std::time::Duration; + + pub fn start_jobs() { + delegation::start(); + } + + mod delegation { + use super::*; + + use crate::controllers::init_controller::InitController; + + // fetch JWKS every 1 hour + const JWKS_FETCH_INTERVAL: Duration = Duration::from_secs(60 * 60); + + pub fn start() { + set_timer_interval(JWKS_FETCH_INTERVAL, || { + spawn(fetch_jwks()); + }); + } + + async fn fetch_jwks() { + InitController::default().fetch_jwks().await + } + } +} diff --git a/apps/ssp_backend/src/controllers/mod.rs b/apps/ssp_backend/src/controllers/mod.rs new file mode 100644 index 0000000..59c11d5 --- /dev/null +++ b/apps/ssp_backend/src/controllers/mod.rs @@ -0,0 +1,5 @@ +mod certificate_controller; +mod config_controller; +mod delegation_controller; +mod init_controller; +mod user_controller; diff --git a/apps/ssp_backend/src/controllers/user_controller.rs b/apps/ssp_backend/src/controllers/user_controller.rs new file mode 100644 index 0000000..b88ba4f --- /dev/null +++ b/apps/ssp_backend/src/controllers/user_controller.rs @@ -0,0 +1,25 @@ +use candid::Principal; +use ic_cdk::{caller, query, trap}; + +use crate::services::UserService; + +#[query] +fn get_my_user() -> ssp_backend_types::User { + let calling_principal = caller(); + + UserController::default().get_my_user(calling_principal) +} + +#[derive(Default)] +pub struct UserController { + user_service: UserService, +} + +impl UserController { + fn get_my_user(&self, calling_principal: Principal) -> ssp_backend_types::User { + match self.user_service.get_user(&calling_principal) { + Some(user) => user.into(), + None => trap("No user found"), + } + } +} diff --git a/apps/ssp_backend/src/lib.rs b/apps/ssp_backend/src/lib.rs new file mode 100644 index 0000000..551c9d2 --- /dev/null +++ b/apps/ssp_backend/src/lib.rs @@ -0,0 +1,43 @@ +mod controllers; +mod mappings; +mod repositories; +mod services; +mod system_api; +mod utils; + +use candid::{export_service, Principal}; +use ic_cdk::query; +use ssp_backend_types::*; + +// In the following, we register a custom getrandom implementation because +// otherwise getrandom (which is a dependency of some packages) fails to compile. +// This is necessary because getrandom by default fails to compile for the +// wasm32-unknown-unknown target (which is required for deploying a canister). +getrandom::register_custom_getrandom!(always_fail); +pub fn always_fail(_buf: &mut [u8]) -> Result<(), getrandom::Error> { + Err(getrandom::Error::UNSUPPORTED) +} + +export_service!(); +#[query(name = "__get_candid_interface_tmp_hack")] +fn export_candid() -> String { + __export_service() +} + +#[cfg(test)] +mod tests { + use super::*; + use candid_parser::utils::{service_compatible, CandidSource}; + use std::path::Path; + + #[test] + fn check_candid_interface() { + let new_interface = __export_service(); + + service_compatible( + CandidSource::Text(&new_interface), + CandidSource::File(Path::new("./ssp_backend.did")), + ) + .unwrap(); + } +} diff --git a/apps/ssp_backend/src/mappings/certificate.rs b/apps/ssp_backend/src/mappings/certificate.rs new file mode 100644 index 0000000..a2ba761 --- /dev/null +++ b/apps/ssp_backend/src/mappings/certificate.rs @@ -0,0 +1,73 @@ +use ssp_backend_types::CreateCertificateResponse; + +use crate::repositories::{Certificate, CertificateContent, CertificateId, DateTime}; + +impl From for ssp_backend_types::CertificateContent { + fn from(value: CertificateContent) -> Self { + ssp_backend_types::CertificateContent { + name: value.name, + issued_at: value.issued_at.to_string(), + sport_category: value.sport_category, + notes: value.notes, + file_uri: value.file_uri, + external_id: value.external_id, + issuer_full_name: value.issuer_full_name, + issuer_club_name: value.issuer_club_name, + } + } +} + +impl TryFrom for CertificateContent { + type Error = String; + + fn try_from(value: ssp_backend_types::CreateCertificateContentRequest) -> Result { + let issued_at = DateTime::from_timestamp_micros(value.issued_at)?; + Ok(CertificateContent { + name: value.name, + issued_at, + sport_category: value.sport_category, + notes: value.notes, + file_uri: value.file_uri, + external_id: value.external_id, + issuer_full_name: value.issuer_full_name, + issuer_club_name: value.issuer_club_name, + }) + } +} + +impl From for ssp_backend_types::Certificate { + fn from(value: Certificate) -> Self { + ssp_backend_types::Certificate { + user_principal: value.user_principal, + created_at: value.created_at.to_string(), + content: value.content.into(), + managed_user_id: value.managed_user_id.map(|id| id.to_string()), + } + } +} + +pub fn map_create_certificate_response(certificate_id: CertificateId) -> CreateCertificateResponse { + CreateCertificateResponse { + id: certificate_id.to_string(), + } +} + +pub fn map_certificate_preview_with_id( + id: CertificateId, + certificate: Certificate, +) -> ssp_backend_types::CertificatePreviewWithId { + ssp_backend_types::CertificatePreviewWithId { + id: id.to_string(), + name: certificate.content.name, + } +} + +pub fn map_certificate_with_id( + id: CertificateId, + certificate: Certificate, +) -> ssp_backend_types::CertificateWithId { + ssp_backend_types::CertificateWithId { + id: id.to_string(), + certificate_cbor_hex: certificate.certificate_cbor_hex(), + } +} diff --git a/apps/ssp_backend/src/mappings/config.rs b/apps/ssp_backend/src/mappings/config.rs new file mode 100644 index 0000000..d8fb6f2 --- /dev/null +++ b/apps/ssp_backend/src/mappings/config.rs @@ -0,0 +1,9 @@ +use crate::repositories::Config; + +impl From for ssp_backend_types::Config { + fn from(value: Config) -> Self { + Self { + backend_principal: value.backend_principal, + } + } +} diff --git a/apps/ssp_backend/src/mappings/mod.rs b/apps/ssp_backend/src/mappings/mod.rs new file mode 100644 index 0000000..01e2dde --- /dev/null +++ b/apps/ssp_backend/src/mappings/mod.rs @@ -0,0 +1,5 @@ +mod certificate; +mod config; +mod user; + +pub use certificate::*; diff --git a/apps/ssp_backend/src/mappings/user.rs b/apps/ssp_backend/src/mappings/user.rs new file mode 100644 index 0000000..b8b0b71 --- /dev/null +++ b/apps/ssp_backend/src/mappings/user.rs @@ -0,0 +1,11 @@ +use crate::repositories::User; + +impl From for ssp_backend_types::User { + fn from(user: User) -> Self { + Self { + sub: user.jwt_sub, + db_id: user.db_id.to_string(), + created_at: user.created_at.to_string(), + } + } +} diff --git a/apps/ssp_backend/src/repositories/certificate_repository.rs b/apps/ssp_backend/src/repositories/certificate_repository.rs new file mode 100644 index 0000000..22f9581 --- /dev/null +++ b/apps/ssp_backend/src/repositories/certificate_repository.rs @@ -0,0 +1,209 @@ +use std::cell::RefCell; + +use candid::Principal; +use ic_cdk::println; +use ic_certification::{labeled, labeled_hash, AsHashTree, Hash, RbTree}; +use ic_stable_structures::Storable; +use serde::Serialize; +use serde_cbor::Serializer; + +const SSP_CERTIFICATES_TREE_LABEL: &[u8] = b"ssp_certificates"; + +use super::{ + init_certificate_managed_user_id_index, init_certificate_user_principal_index, + init_certificates, Certificate, CertificateId, CertificateManagedUserIdIndexMemory, + CertificateManagedUserIdKey, CertificateManagedUserIdRange, CertificateMemory, + CertificateUserPrincipalIndexMemory, CertificateUserPrincipalKey, + CertificateUserPrincipalRange, Uuid, +}; + +/// SSP certificates tree structure: +/// ssp_certificates +/// └── +/// └── +/// └── certificate cbor data hash +type IcCertificateTree = RbTree>; + +struct CertificateState { + certificates: CertificateMemory, + certificate_user_principal_index: CertificateUserPrincipalIndexMemory, + certificate_managed_user_id_index: CertificateManagedUserIdIndexMemory, + ic_certificate_tree: IcCertificateTree, +} + +impl Default for CertificateState { + fn default() -> Self { + Self { + certificates: init_certificates(), + certificate_user_principal_index: init_certificate_user_principal_index(), + certificate_managed_user_id_index: init_certificate_managed_user_id_index(), + ic_certificate_tree: RbTree::new(), + } + } +} + +thread_local! { + static STATE: RefCell = RefCell::new(CertificateState::default()); +} + +pub struct UserCertificateWithCertification { + pub certificate: Option, + pub ic_certificate: Vec, + pub ic_certificate_witness: Vec, +} + +fn ic_certificate() -> Vec { + ic_cdk::api::data_certificate().expect("No data certificate available") +} + +#[derive(Default)] +pub struct CertificateRepository {} + +impl CertificateRepository { + pub fn get_certificate(&self, id: &CertificateId) -> UserCertificateWithCertification { + match STATE.with_borrow(|s| s.certificates.get(id)) { + Some(certificate) => { + let ic_certificate_witness = + self.certificate_witness(&certificate.user_principal, Some(id)); + + UserCertificateWithCertification { + certificate: Some(certificate), + ic_certificate: ic_certificate(), + ic_certificate_witness, + } + } + None => UserCertificateWithCertification { + certificate: None, + ic_certificate: vec![], + ic_certificate_witness: vec![], + }, + } + } + + pub fn get_certificates_by_user_principal( + &self, + user_principal: &Principal, + ) -> Result, String> { + let range = CertificateUserPrincipalRange::new(*user_principal)?; + + let certificates = STATE.with_borrow(|s| { + s.certificate_user_principal_index + .range(range) + .map(|(_, id)| (id, s.certificates.get(&id).unwrap())) + .collect() + }); + + Ok(certificates) + } + + pub fn get_certificates_by_managed_user_id( + &self, + managed_user_id: &Uuid, + ) -> Result, String> { + let range = CertificateManagedUserIdRange::new(*managed_user_id)?; + + let certificates: Vec<_> = STATE.with_borrow(|s| { + s.certificate_managed_user_id_index + .range(range) + .map(|(_, id)| (id, s.certificates.get(&id).unwrap())) + .collect() + }); + + Ok(certificates) + } + + pub async fn create_certificate( + &self, + certificate: Certificate, + ) -> Result { + let id = CertificateId::new().await?; + let user_principal = certificate.user_principal; + let user_principal_key = CertificateUserPrincipalKey::new(user_principal, id)?; + + STATE.with_borrow_mut(|s| { + s.certificates.insert(id, certificate.clone()); + s.certificate_user_principal_index + .insert(user_principal_key, id); + + if let Some(managed_user_id) = certificate.managed_user_id { + let managed_user_id_key = CertificateManagedUserIdKey::new(managed_user_id, id)?; + + s.certificate_managed_user_id_index + .insert(managed_user_id_key, id); + } + self.certify_certificate_data(&mut s.ic_certificate_tree, id, certificate); + + Ok::<(), String>(()) + })?; + + self.set_certified_data(); + + Ok(id) + } + + pub fn certify_all_certificates(&self) { + let count = STATE.with_borrow_mut(|s| { + s.certificates.iter().for_each(|(id, certificate)| { + self.certify_certificate_data(&mut s.ic_certificate_tree, id, certificate); + }); + s.certificates.len() + }); + self.set_certified_data(); + println!("Certified {} certificates", count); + } + + fn certify_certificate_data( + &self, + ic_certificate_tree: &mut IcCertificateTree, + id: CertificateId, + certificate: Certificate, + ) { + let user_principal = certificate.user_principal; + let user_principal_bytes = user_principal.to_bytes(); + match ic_certificate_tree.get(&user_principal_bytes) { + Some(_) => { + ic_certificate_tree.modify(&user_principal_bytes, |inner| { + inner.insert(id, certificate.root_hash()) + }); + } + None => { + let mut tree = RbTree::new(); + tree.insert(id, certificate.root_hash()); + ic_certificate_tree.insert(user_principal, tree); + } + } + } + + fn set_certified_data(&self) { + STATE.with_borrow(|s| { + let tree_hash = s.ic_certificate_tree.root_hash(); + let root_hash = labeled_hash(SSP_CERTIFICATES_TREE_LABEL, &tree_hash); + ic_cdk::api::set_certified_data(&root_hash); + }); + } + + fn certificate_witness( + &self, + user_principal: &Principal, + certificate_id: Option<&CertificateId>, + ) -> Vec { + STATE.with_borrow(|s| { + let witness = match certificate_id { + Some(id) => s + .ic_certificate_tree + .nested_witness(user_principal.as_ref(), |inner| inner.witness(id.as_ref())), + None => s + .ic_certificate_tree + .nested_witness(user_principal.as_ref(), |inner| inner.keys()), + }; + let tree = labeled(SSP_CERTIFICATES_TREE_LABEL, witness); + + let mut data = vec![]; + let mut serializer = Serializer::new(&mut data); + serializer.self_describe().unwrap(); + tree.serialize(&mut serializer).unwrap(); + + data + }) + } +} diff --git a/apps/ssp_backend/src/repositories/config_repository.rs b/apps/ssp_backend/src/repositories/config_repository.rs new file mode 100644 index 0000000..c30fa27 --- /dev/null +++ b/apps/ssp_backend/src/repositories/config_repository.rs @@ -0,0 +1,38 @@ +use std::cell::RefCell; + +use super::{init_config, Config, ConfigMemory}; + +struct ConfigState { + config: ConfigMemory, +} + +impl Default for ConfigState { + fn default() -> Self { + Self { + config: init_config(), + } + } +} + +thread_local! { + static STATE: RefCell = RefCell::new(ConfigState::default()); +} + +#[derive(Default)] +pub struct ConfigRepository {} + +impl ConfigRepository { + pub fn get_config(&self) -> Config { + STATE.with_borrow(|s| s.config.get().clone()) + } + + pub fn set_config(&self, config: Config) -> Result<(), String> { + STATE.with_borrow_mut(|s| { + s.config + .set(config) + .map_err(|err| format!("Cannot set config: {:?}", err)) + })?; + + Ok(()) + } +} diff --git a/apps/ssp_backend/src/repositories/delegation_repository.rs b/apps/ssp_backend/src/repositories/delegation_repository.rs new file mode 100644 index 0000000..ce52852 --- /dev/null +++ b/apps/ssp_backend/src/repositories/delegation_repository.rs @@ -0,0 +1,60 @@ +use std::cell::RefCell; + +use canister_sig_util::signature_map::SignatureMap; +use ic_certification::Hash; +use ssp_backend_types::Auth0JWKSet; + +use super::{init_salt, Salt, SaltMemory}; + +pub struct DelegationState { + sigs: SignatureMap, + jwks: Option, + salt: SaltMemory, +} + +impl Default for DelegationState { + fn default() -> Self { + Self { + sigs: SignatureMap::default(), + jwks: None, + salt: init_salt(), + } + } +} + +thread_local! { + static STATE: RefCell = RefCell::new(DelegationState::default()); +} + +#[derive(Default)] +pub struct DelegationRepository {} + +impl DelegationRepository { + pub fn get_salt(&self) -> Salt { + STATE.with_borrow(|s| s.salt.get().to_owned()) + } + + pub fn set_salt(&self, salt: Salt) { + STATE.with_borrow_mut(|s| s.salt.set(salt).unwrap()); + } + + pub fn set_jwks(&self, jwks: Auth0JWKSet) { + STATE.with_borrow_mut(|s| s.jwks = Some(jwks)); + } + + pub fn get_jwks(&self) -> Option { + STATE.with_borrow(|s| s.jwks.clone()) + } + + pub fn add_delegation_signature(&self, seed: &[u8], message_hash: Hash) { + STATE.with_borrow_mut(|s| s.sigs.add_signature(seed, message_hash)); + } + + pub fn get_signature(&self, seed: &[u8], message_hash: Hash) -> Result, String> { + STATE.with_borrow(|s| s.sigs.get_signature_as_cbor(seed, message_hash, None)) + } + + pub fn get_sigs_root_hash(&self) -> Hash { + STATE.with_borrow(|s| s.sigs.root_hash()) + } +} diff --git a/apps/ssp_backend/src/repositories/memories/certificate_memory.rs b/apps/ssp_backend/src/repositories/memories/certificate_memory.rs new file mode 100644 index 0000000..2f9ea61 --- /dev/null +++ b/apps/ssp_backend/src/repositories/memories/certificate_memory.rs @@ -0,0 +1,40 @@ +use ic_stable_structures::BTreeMap; + +use crate::repositories::{ + Certificate, CertificateId, CertificateManagedUserIdKey, CertificateUserPrincipalKey, +}; + +use super::{ + Memory, CERTIFICATE_MANAGED_USER_ID_INDEX_MEMORY_ID, CERTIFICATE_MEMORY_ID, + CERTIFICATE_USER_PRINCIPAL_INDEX_MEMORY_ID, MEMORY_MANAGER, +}; + +pub type CertificateMemory = BTreeMap; +pub type CertificateUserPrincipalIndexMemory = + BTreeMap; +pub type CertificateManagedUserIdIndexMemory = + BTreeMap; + +pub fn init_certificates() -> CertificateMemory { + BTreeMap::init(get_certificates_memory()) +} + +pub fn init_certificate_user_principal_index() -> CertificateUserPrincipalIndexMemory { + BTreeMap::init(get_certificate_user_principal_index_memory()) +} + +pub fn init_certificate_managed_user_id_index() -> CertificateManagedUserIdIndexMemory { + BTreeMap::init(get_certificate_managed_user_id_index_memory()) +} + +fn get_certificates_memory() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(CERTIFICATE_MEMORY_ID)) +} + +fn get_certificate_user_principal_index_memory() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(CERTIFICATE_USER_PRINCIPAL_INDEX_MEMORY_ID)) +} + +fn get_certificate_managed_user_id_index_memory() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(CERTIFICATE_MANAGED_USER_ID_INDEX_MEMORY_ID)) +} diff --git a/apps/ssp_backend/src/repositories/memories/config_memory.rs b/apps/ssp_backend/src/repositories/memories/config_memory.rs new file mode 100644 index 0000000..a094021 --- /dev/null +++ b/apps/ssp_backend/src/repositories/memories/config_memory.rs @@ -0,0 +1,15 @@ +use ic_stable_structures::Cell; + +use crate::repositories::Config; + +use super::{Memory, CONFIG_MEMORY_ID, MEMORY_MANAGER}; + +pub type ConfigMemory = Cell; + +pub fn init_config() -> ConfigMemory { + ConfigMemory::init(get_config_memory(), Config::default()).unwrap() +} + +fn get_config_memory() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(CONFIG_MEMORY_ID)) +} diff --git a/apps/ssp_backend/src/repositories/memories/delegation_memory.rs b/apps/ssp_backend/src/repositories/memories/delegation_memory.rs new file mode 100644 index 0000000..0d64e6b --- /dev/null +++ b/apps/ssp_backend/src/repositories/memories/delegation_memory.rs @@ -0,0 +1,15 @@ +use ic_stable_structures::Cell; + +use crate::repositories::{Salt, EMPTY_SALT}; + +use super::{Memory, MEMORY_MANAGER, SALT_MEMORY_ID}; + +pub type SaltMemory = Cell; + +pub fn init_salt() -> SaltMemory { + SaltMemory::init(get_salt_memory(), EMPTY_SALT).unwrap() +} + +fn get_salt_memory() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(SALT_MEMORY_ID)) +} diff --git a/apps/ssp_backend/src/repositories/memories/memory_manager.rs b/apps/ssp_backend/src/repositories/memories/memory_manager.rs new file mode 100644 index 0000000..8bd8018 --- /dev/null +++ b/apps/ssp_backend/src/repositories/memories/memory_manager.rs @@ -0,0 +1,19 @@ +use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory}; +use ic_stable_structures::DefaultMemoryImpl; +use std::cell::RefCell; + +pub(super) type Memory = VirtualMemory; + +thread_local! { + pub static MEMORY_MANAGER: RefCell> = + RefCell::new(MemoryManager::init(DefaultMemoryImpl::default())); +} + +pub(super) const SALT_MEMORY_ID: MemoryId = MemoryId::new(0); +pub(super) const USERS_MEMORY_ID: MemoryId = MemoryId::new(1); +pub(super) const USER_SUB_INDEX_MEMORY_ID: MemoryId = MemoryId::new(2); +pub(super) const USER_DB_ID_INDEX_MEMORY_ID: MemoryId = MemoryId::new(3); +pub(super) const CONFIG_MEMORY_ID: MemoryId = MemoryId::new(4); +pub(super) const CERTIFICATE_MEMORY_ID: MemoryId = MemoryId::new(5); +pub(super) const CERTIFICATE_USER_PRINCIPAL_INDEX_MEMORY_ID: MemoryId = MemoryId::new(6); +pub(super) const CERTIFICATE_MANAGED_USER_ID_INDEX_MEMORY_ID: MemoryId = MemoryId::new(7); diff --git a/apps/ssp_backend/src/repositories/memories/mod.rs b/apps/ssp_backend/src/repositories/memories/mod.rs new file mode 100644 index 0000000..baa00d6 --- /dev/null +++ b/apps/ssp_backend/src/repositories/memories/mod.rs @@ -0,0 +1,12 @@ +mod certificate_memory; +mod config_memory; +mod delegation_memory; +mod memory_manager; +mod user_memory; + +use memory_manager::*; + +pub(super) use certificate_memory::*; +pub(super) use config_memory::*; +pub(super) use delegation_memory::*; +pub(super) use user_memory::*; diff --git a/apps/ssp_backend/src/repositories/memories/user_memory.rs b/apps/ssp_backend/src/repositories/memories/user_memory.rs new file mode 100644 index 0000000..1a764f0 --- /dev/null +++ b/apps/ssp_backend/src/repositories/memories/user_memory.rs @@ -0,0 +1,35 @@ +use ic_stable_structures::BTreeMap; + +use crate::repositories::{User, UserDbId, UserPrincipal, UserSub}; + +use super::{ + Memory, MEMORY_MANAGER, USERS_MEMORY_ID, USER_DB_ID_INDEX_MEMORY_ID, USER_SUB_INDEX_MEMORY_ID, +}; + +pub type UserMemory = BTreeMap; +pub type UserSubIndexMemory = BTreeMap; +pub type UserDbIdIndexMemory = BTreeMap; + +pub fn init_users() -> UserMemory { + UserMemory::init(get_users_memory()) +} + +pub fn init_user_sub_index() -> UserSubIndexMemory { + UserSubIndexMemory::init(get_user_sub_index_memory()) +} + +pub fn init_user_db_id_index() -> UserDbIdIndexMemory { + UserDbIdIndexMemory::init(get_user_db_id_index_memory()) +} + +fn get_users_memory() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(USERS_MEMORY_ID)) +} + +fn get_user_sub_index_memory() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(USER_SUB_INDEX_MEMORY_ID)) +} + +fn get_user_db_id_index_memory() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(USER_DB_ID_INDEX_MEMORY_ID)) +} diff --git a/apps/ssp_backend/src/repositories/mod.rs b/apps/ssp_backend/src/repositories/mod.rs new file mode 100644 index 0000000..fb4ac5d --- /dev/null +++ b/apps/ssp_backend/src/repositories/mod.rs @@ -0,0 +1,13 @@ +mod certificate_repository; +mod config_repository; +mod delegation_repository; +mod memories; +mod types; +mod user_repository; + +pub use certificate_repository::*; +pub use config_repository::*; +pub use delegation_repository::*; +use memories::*; +pub use types::*; +pub use user_repository::*; diff --git a/apps/ssp_backend/src/repositories/types/certificate.rs b/apps/ssp_backend/src/repositories/types/certificate.rs new file mode 100644 index 0000000..ac914fa --- /dev/null +++ b/apps/ssp_backend/src/repositories/types/certificate.rs @@ -0,0 +1,222 @@ +use std::{borrow::Cow, ops::RangeBounds}; + +use candid::{CandidType, Decode, Deserialize, Encode, Principal}; +use ic_certification::{leaf, leaf_hash, AsHashTree, Hash, HashTree}; +use ic_stable_structures::{ + storable::{Blob, Bound}, + Storable, +}; +use serde::Serialize; + +use crate::utils::cbor_serialize; + +use super::{DateTime, Uuid}; + +pub type CertificateId = Uuid; + +#[derive(Debug, CandidType, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CertificateContent { + pub external_id: Option, + pub file_uri: Option, + pub issued_at: DateTime, + pub issuer_club_name: Option, + pub issuer_full_name: Option, + pub name: String, + pub notes: Option, + pub sport_category: String, +} + +#[derive(Debug, CandidType, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Certificate { + pub content: CertificateContent, + pub created_at: DateTime, + pub managed_user_id: Option, + pub user_principal: Principal, +} + +impl Certificate { + pub fn certificate_cbor(&self) -> Vec { + cbor_serialize(&self).unwrap() + } + + pub fn certificate_cbor_hex(&self) -> String { + hex::encode(self.certificate_cbor()) + } +} + +impl AsHashTree for Certificate { + fn root_hash(&self) -> Hash { + let serialized = self.certificate_cbor(); + leaf_hash(&serialized[..]) + } + fn as_hash_tree(&self) -> HashTree { + leaf(Cow::from(self.certificate_cbor())) + } +} + +impl Storable for Certificate { + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(Encode!(self).unwrap()) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + Decode!(bytes.as_ref(), Self).unwrap() + } + + const BOUND: Bound = Bound::Unbounded; +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CertificateUserPrincipalKey(Blob<{ Self::MAX_SIZE as usize }>); + +impl CertificateUserPrincipalKey { + const MAX_SIZE: u32 = <(Principal, CertificateId)>::BOUND.max_size(); + + pub fn new(user_principal: Principal, certificate_id: CertificateId) -> Result { + Ok(Self( + Blob::try_from((user_principal, certificate_id).to_bytes().as_ref()).map_err(|_| { + format!( + "Failed to convert user principal {:?} and certificate id {:?} to bytes.", + user_principal, certificate_id + ) + })?, + )) + } +} + +impl Storable for CertificateUserPrincipalKey { + fn to_bytes(&self) -> Cow<[u8]> { + self.0.to_bytes() + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + Self(Blob::from_bytes(bytes)) + } + + const BOUND: Bound = Bound::Bounded { + max_size: Self::MAX_SIZE, + is_fixed_size: true, + }; +} + +pub struct CertificateUserPrincipalRange { + start_bound: CertificateUserPrincipalKey, + end_bound: CertificateUserPrincipalKey, +} + +impl CertificateUserPrincipalRange { + pub fn new(user_principal: Principal) -> Result { + Ok(Self { + start_bound: CertificateUserPrincipalKey::new(user_principal, CertificateId::min())?, + end_bound: CertificateUserPrincipalKey::new(user_principal, CertificateId::max())?, + }) + } +} + +impl RangeBounds for CertificateUserPrincipalRange { + fn start_bound(&self) -> std::ops::Bound<&CertificateUserPrincipalKey> { + std::ops::Bound::Included(&self.start_bound) + } + + fn end_bound(&self) -> std::ops::Bound<&CertificateUserPrincipalKey> { + std::ops::Bound::Included(&self.end_bound) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CertificateManagedUserIdKey(Blob<{ Self::MAX_SIZE as usize }>); + +impl CertificateManagedUserIdKey { + const MAX_SIZE: u32 = <(Uuid, CertificateId)>::BOUND.max_size(); + + pub fn new(managed_user_id: Uuid, certificate_id: CertificateId) -> Result { + Ok(Self( + Blob::try_from((managed_user_id, certificate_id).to_bytes().as_ref()).map_err( + |_| { + format!( + "Failed to convert managed user id {:?} and certificate id {:?} to bytes.", + managed_user_id, certificate_id + ) + }, + )?, + )) + } +} + +impl Storable for CertificateManagedUserIdKey { + fn to_bytes(&self) -> Cow<[u8]> { + self.0.to_bytes() + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + Self(Blob::from_bytes(bytes)) + } + + const BOUND: Bound = Bound::Bounded { + max_size: Self::MAX_SIZE, + is_fixed_size: true, + }; +} + +pub struct CertificateManagedUserIdRange { + start_bound: CertificateManagedUserIdKey, + end_bound: CertificateManagedUserIdKey, +} + +impl CertificateManagedUserIdRange { + pub fn new(managed_user_id: Uuid) -> Result { + Ok(Self { + start_bound: CertificateManagedUserIdKey::new(managed_user_id, CertificateId::min())?, + end_bound: CertificateManagedUserIdKey::new(managed_user_id, CertificateId::max())?, + }) + } +} + +impl RangeBounds for CertificateManagedUserIdRange { + fn start_bound(&self) -> std::ops::Bound<&CertificateManagedUserIdKey> { + std::ops::Bound::Included(&self.start_bound) + } + + fn end_bound(&self) -> std::ops::Bound<&CertificateManagedUserIdKey> { + std::ops::Bound::Included(&self.end_bound) + } +} + +#[cfg(test)] +mod test { + use crate::system_api::get_date_time; + + use super::*; + use rstest::*; + + #[rstest] + fn storable_impl() { + let certificate = certificate(); + let serialized_certificate = certificate.to_bytes(); + let deserialized_certificate = Certificate::from_bytes(serialized_certificate); + + assert_eq!(certificate, deserialized_certificate); + } + + fn certificate() -> Certificate { + let date_time = DateTime::new(get_date_time().unwrap()).unwrap(); + Certificate { + user_principal: Principal::from_text( + "63ubj-icu27-xedai-mj7py-uj2uw-pygtr-ckarq-owt2g-fhbcc-c4urf-tqe", + ) + .unwrap(), + created_at: date_time, + content: CertificateContent { + name: "name".to_string(), + issued_at: date_time, + sport_category: "sport_category".to_string(), + notes: None, + file_uri: Some("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA".to_string()), + external_id: None, + issuer_full_name: None, + issuer_club_name: None, + }, + managed_user_id: None, + } + } +} diff --git a/apps/ssp_backend/src/repositories/types/config.rs b/apps/ssp_backend/src/repositories/types/config.rs new file mode 100644 index 0000000..11da57f --- /dev/null +++ b/apps/ssp_backend/src/repositories/types/config.rs @@ -0,0 +1,44 @@ +use std::borrow::Cow; + +use candid::{CandidType, Decode, Deserialize, Encode, Principal}; +use ic_stable_structures::{storable::Bound, Storable}; + +#[derive(Debug, Default, CandidType, Deserialize, Clone, PartialEq, Eq)] +pub struct Config { + /// The off-chain backend principal. + pub backend_principal: Option, +} + +impl Storable for Config { + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(Encode!(self).unwrap()) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + Decode!(bytes.as_ref(), Self).unwrap() + } + + const BOUND: Bound = Bound::Unbounded; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn storable_impl() { + let config = Config { + // a random principal + backend_principal: Some(backend_principal()), + }; + let serialized_config = config.to_bytes(); + let deserialized_config = Config::from_bytes(serialized_config); + + assert_eq!(config, deserialized_config); + } + + fn backend_principal() -> Principal { + Principal::from_text("63ubj-icu27-xedai-mj7py-uj2uw-pygtr-ckarq-owt2g-fhbcc-c4urf-tqe") + .unwrap() + } +} diff --git a/apps/ssp_backend/src/repositories/types/date_time.rs b/apps/ssp_backend/src/repositories/types/date_time.rs new file mode 100644 index 0000000..b238389 --- /dev/null +++ b/apps/ssp_backend/src/repositories/types/date_time.rs @@ -0,0 +1,154 @@ +use candid::{ + types::{Type, TypeInner}, + CandidType, Deserialize, +}; +use chrono::{Datelike, Timelike}; +use ic_stable_structures::{storable::Bound, Storable}; +use serde::Serialize; +use std::{borrow::Cow, str::FromStr}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct DateTime(chrono::DateTime); + +const DATE_TIME_SIZE: u32 = 25; + +impl DateTime { + pub fn new(date_time: chrono::DateTime) -> Result { + Ok(Self(date_time.with_nanosecond(0).ok_or(&format!( + "Failed to convert date time {:?}", + date_time + ))?)) + } + + pub fn from_timestamp_micros(micros: u64) -> Result { + let micros = micros + .try_into() + .map_err(|err| format!("Failed to convert timestamp {} to micros: {}", micros, err))?; + let dt = chrono::DateTime::from_timestamp_micros(micros).ok_or(&format!( + "Failed to convert timestamp {} to date time", + micros + ))?; + Self::new(dt) + } + + pub fn sub(&self, duration: chrono::Duration) -> Self { + Self(self.0 - duration) + } + + pub fn min() -> Self { + Self(chrono::DateTime::::UNIX_EPOCH) + } + + pub fn max() -> Result { + Ok(Self( + chrono::DateTime::::MAX_UTC + .with_year(9999) + .ok_or_else(|| "Failed to create max date time.".to_string())?, + )) + } + + pub fn timestamp_micros(&self) -> u64 { + self.0.timestamp_micros().try_into().unwrap() + } +} + +impl ToString for DateTime { + fn to_string(&self) -> String { + self.0.to_rfc3339_opts(chrono::SecondsFormat::Secs, false) + } +} + +impl CandidType for DateTime { + fn _ty() -> Type { + TypeInner::Text.into() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: candid::types::Serializer, + { + self.to_string().idl_serialize(serializer) + } +} + +impl Serialize for DateTime { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for DateTime { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer) + .and_then(|date_time| { + chrono::DateTime::parse_from_rfc3339(&date_time) + .map_err(|_| serde::de::Error::custom("Invalid date time.")) + }) + .map(|date_time| Self(date_time.into())) + } +} + +impl Storable for DateTime { + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(self.to_string().as_bytes().to_vec()) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + Self(chrono::DateTime::from_str(&String::from_utf8(bytes.into_owned()).unwrap()).unwrap()) + } + + const BOUND: Bound = Bound::Bounded { + max_size: DATE_TIME_SIZE, + is_fixed_size: true, + }; +} + +#[cfg(test)] +mod tests { + use super::*; + use chrono::{FixedOffset, NaiveDate, TimeZone, Utc}; + use rstest::*; + + #[rstest] + fn storable_impl_admin() { + let date_time = date_time(); + let serialized_date_time = date_time.to_bytes(); + let deserialized_date_time = DateTime::from_bytes(serialized_date_time); + + assert_eq!(date_time, deserialized_date_time); + } + + #[rstest] + fn date_time_timestamp() { + let (timestamp, date_string) = timestamp_micros(); + let date_time = DateTime::from_timestamp_micros(timestamp).unwrap(); + + assert_eq!(date_time.to_string(), date_string); + assert_eq!(date_time.timestamp_micros(), timestamp); + } + + fn timestamp_micros() -> (u64, String) { + (1706899350000000, "2024-02-02T18:42:30+00:00".to_string()) + } + + const HOUR: i32 = 3600; + + fn date_time() -> DateTime { + let tz = FixedOffset::east_opt(5 * HOUR).unwrap(); + let date_time = NaiveDate::from_ymd_opt(2021, 12, 4) + .unwrap() + .and_hms_opt(10, 20, 6) + .unwrap() + .and_local_timezone(tz) + .unwrap() + .naive_local(); + + DateTime::new(Utc.from_local_datetime(&date_time).unwrap()).unwrap() + } +} diff --git a/apps/ssp_backend/src/repositories/types/id_token.rs b/apps/ssp_backend/src/repositories/types/id_token.rs new file mode 100644 index 0000000..25aba30 --- /dev/null +++ b/apps/ssp_backend/src/repositories/types/id_token.rs @@ -0,0 +1,198 @@ +use base64::{engine::general_purpose, Engine}; +use jsonwebtoken_rustcrypto::{ + crypto::verify, decode_header, errors::ErrorKind, Algorithm, DecodingKey, TokenData, +}; +use serde::{Deserialize, Serialize}; +use ssp_backend_types::{Auth0JWKSet, HasuraJWTClaims}; + +use crate::system_api::{unix_timestamp, NANOS_IN_SECONDS}; + +/// The maximum age of an ID token (checked against the `iat` claim). +/// This value is arbitrary and should be reasonably small. +const MAX_IAT_AGE_SECONDS: u64 = 10 * 60; // 10 minutes + +// ignore rust-analyzer errors on these environment variables +// compilation succeeds if you've correctly set the .env file +pub const AUTH0_ISSUER: &str = env!("ID_TOKEN_ISSUER_BASE_URL"); // expected to have a trailing slash +const AUTH0_AUDIENCE: &str = env!("ID_TOKEN_AUDIENCE"); + +pub type IdToken = TokenData; +pub type IdTokenResult = std::result::Result; + +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct JWTClaims { + pub iss: String, + pub aud: String, + /// Issued at (seconds since unix epoch) + pub iat: u64, + /// Expires at (seconds since unix epoch) + pub exp: u64, + pub sub: String, + pub nonce: String, + #[serde( + rename = "https://hasura.io/jwt/claims", + skip_serializing_if = "Option::is_none" + )] + pub hasura_claims: Option, +} + +impl JWTClaims { + pub fn expiration_timestamp_ns(&self) -> u64 { + self.exp * NANOS_IN_SECONDS + } + + pub fn validate(&self) -> Result<(), ValidationError> { + let time = unix_timestamp(); + + if self.exp < time { + return Err(ValidationError::TokenExpired); + } + + if self.iat + MAX_IAT_AGE_SECONDS < time { + return Err(ValidationError::IatTooOld); + } + + if self.iss != AUTH0_ISSUER { + return Err(ValidationError::IssuerMismatch); + } + + if self.aud != AUTH0_AUDIENCE { + return Err(ValidationError::AudienceMismatch); + } + + Ok(()) + } + + pub fn user_id(&self) -> Option { + self.hasura_claims.clone().map(|c| c.x_hasura_user_id) + } +} + +#[derive(Debug)] +pub enum ValidationError { + TokenExpired, + IatTooOld, + IssuerMismatch, + AudienceMismatch, +} + +/// Takes the result of a rsplit and ensure we only get 2 parts +/// Errors if we don't +macro_rules! expect_two { + ($iter:expr) => {{ + let mut i = $iter; + match (i.next(), i.next(), i.next()) { + (Some(first), Some(second), None) => (first, second), + _ => return Err(ErrorKind::InvalidToken), + } + }}; +} + +pub fn decode_jwt( + token: &str, + expected_alg: Algorithm, + jwks: Option<&Auth0JWKSet>, +) -> IdTokenResult { + let (signature, message) = expect_two!(token.rsplitn(2, '.')); + let (claims, _) = expect_two!(message.rsplitn(2, '.')); + + let jwks = jwks.ok_or(ErrorKind::NoWorkingKey)?; + + let header = decode_header(token).map_err(|e| e.into_kind())?; + let key_id = header.jwk_set_headers.kid.as_ref().unwrap(); + let jwk = jwks.find_key(key_id).unwrap(); + let key = DecodingKey::from_rsa_components(&jwk.n, &jwk.e).map_err(|e| e.into_kind())?; + let header_alg = header + .general_headers + .alg + .ok_or(ErrorKind::InvalidAlgorithm)?; + + if expected_alg != header_alg { + return Err(ErrorKind::InvalidAlgorithm); + } + + if !verify(signature, message, &key, header_alg).map_err(|e| e.into_kind())? { + return Err(ErrorKind::InvalidSignature); + } + + let decoded_claims = String::from_utf8(base64_decode(claims)?).map_err(ErrorKind::Utf8)?; + let claims: JWTClaims = serde_json::from_str(&decoded_claims).map_err(ErrorKind::Json)?; + + Ok(IdToken { header, claims }) +} + +fn base64_decode(input: &str) -> IdTokenResult> { + let engine = general_purpose::URL_SAFE_NO_PAD; + engine.decode(input).map_err(ErrorKind::Base64) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_jwt_claims_serialization_with_hasura_claims() { + let claims = JWTClaims { + iss: "https://example.com/".to_string(), + aud: "audience".to_string(), + iat: 1234567890, + exp: 1234567890, + sub: "6eae002a-f832-414e-835c-18e82a2b11c3".to_string(), + nonce: "nonce123".to_string(), + hasura_claims: Some(HasuraJWTClaims { + x_hasura_default_role: "user".to_string(), + x_hasura_allowed_roles: vec!["user".to_string()], + x_hasura_user_id: "6eae002a-f832-414e-835c-18e82a2b11c3".to_string(), + }), + }; + + let expected_json = json!({ + "iss": "https://example.com/", + "aud": "audience", + "iat": 1234567890, + "exp": 1234567890, + "sub": "6eae002a-f832-414e-835c-18e82a2b11c3", + "nonce": "nonce123", + "https://hasura.io/jwt/claims": { + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user"], + "x-hasura-user-id": "6eae002a-f832-414e-835c-18e82a2b11c3" + } + }); + + let serialized = serde_json::to_string(&claims).unwrap(); + assert_eq!(serialized, serde_json::to_string(&expected_json).unwrap()); + + let deserialized: JWTClaims = serde_json::from_str(&serialized).unwrap(); + assert_eq!(claims, deserialized); + } + + #[test] + fn test_jwt_claims_serialization_without_hasura_claims() { + let claims = JWTClaims { + iss: "https://example.com/".to_string(), + aud: "audience".to_string(), + iat: 1234567890, + exp: 1234567890, + sub: "6eae002a-f832-414e-835c-18e82a2b11c3".to_string(), + nonce: "nonce123".to_string(), + hasura_claims: None, + }; + + let expected_json = json!({ + "iss": "https://example.com/", + "aud": "audience", + "iat": 1234567890, + "exp": 1234567890, + "sub": "6eae002a-f832-414e-835c-18e82a2b11c3", + "nonce": "nonce123" + }); + + let serialized = serde_json::to_string(&claims).unwrap(); + assert_eq!(serialized, serde_json::to_string(&expected_json).unwrap()); + + let deserialized: JWTClaims = serde_json::from_str(&serialized).unwrap(); + assert_eq!(claims, deserialized); + } +} diff --git a/apps/ssp_backend/src/repositories/types/mod.rs b/apps/ssp_backend/src/repositories/types/mod.rs new file mode 100644 index 0000000..cab5546 --- /dev/null +++ b/apps/ssp_backend/src/repositories/types/mod.rs @@ -0,0 +1,15 @@ +mod certificate; +mod config; +mod date_time; +mod id_token; +mod salt; +mod user; +mod uuid; + +pub use certificate::*; +pub use config::*; +pub use date_time::*; +pub use id_token::*; +pub use salt::*; +pub use user::*; +pub use uuid::*; diff --git a/apps/ssp_backend/src/repositories/types/salt.rs b/apps/ssp_backend/src/repositories/types/salt.rs new file mode 100644 index 0000000..e3b14a7 --- /dev/null +++ b/apps/ssp_backend/src/repositories/types/salt.rs @@ -0,0 +1,3 @@ +pub type Salt = [u8; 32]; + +pub const EMPTY_SALT: Salt = [0; 32]; diff --git a/apps/ssp_backend/src/repositories/types/user.rs b/apps/ssp_backend/src/repositories/types/user.rs new file mode 100644 index 0000000..076fd1c --- /dev/null +++ b/apps/ssp_backend/src/repositories/types/user.rs @@ -0,0 +1,66 @@ +use std::borrow::Cow; + +use candid::{CandidType, Decode, Deserialize, Encode, Principal}; +use ic_stable_structures::{storable::Bound, Storable}; + +use crate::system_api::get_date_time; + +use super::{DateTime, Uuid}; + +pub type UserPrincipal = Principal; +pub type UserSub = String; +pub type UserDbId = Uuid; + +#[derive(Debug, CandidType, Deserialize, Clone, PartialEq, Eq)] +pub struct User { + pub jwt_sub: UserSub, + pub db_id: UserDbId, + pub created_at: DateTime, +} + +impl User { + pub fn new(jwt_sub: UserSub, db_id: &str) -> Result { + let datetime = get_date_time()?; + + Ok(Self { + jwt_sub, + db_id: UserDbId::try_from(db_id)?, + created_at: DateTime::new(datetime)?, + }) + } +} + +impl Storable for User { + fn to_bytes(&self) -> Cow<[u8]> { + Cow::Owned(Encode!(self).unwrap()) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + Decode!(bytes.as_ref(), Self).unwrap() + } + + const BOUND: Bound = Bound::Unbounded; +} + +#[cfg(test)] +mod test { + use super::*; + use rstest::*; + + #[rstest] + fn storable_impl() { + let user = user(); + let serialized_user = user.to_bytes(); + let deserialized_user = User::from_bytes(serialized_user); + + assert_eq!(user, deserialized_user); + } + + fn user() -> User { + User::new( + "test_sub".to_string(), + "8c8471a5-b91a-4b8b-9e24-219136ea2b76", + ) + .unwrap() + } +} diff --git a/apps/ssp_backend/src/repositories/types/uuid.rs b/apps/ssp_backend/src/repositories/types/uuid.rs new file mode 100644 index 0000000..b4ab4f4 --- /dev/null +++ b/apps/ssp_backend/src/repositories/types/uuid.rs @@ -0,0 +1,145 @@ +use candid::{ + types::{Serializer, Type, TypeInner}, + CandidType, Deserialize, +}; +use ic_stable_structures::{storable::Bound, Storable}; +use serde::Serialize; +use std::borrow::Cow; +use uuid::{Builder, Uuid as UuidImpl}; + +use crate::system_api::with_random_bytes; + +const UUID_SIZE: usize = 16; + +#[derive(Debug, Clone, Copy, Default, Ord, PartialOrd, PartialEq, Eq)] +pub struct Uuid(UuidImpl); + +impl AsRef<[u8]> for Uuid { + fn as_ref(&self) -> &[u8] { + self.0.as_bytes() + } +} + +impl Uuid { + pub async fn new() -> Result { + with_random_bytes(|bytes: [u8; UUID_SIZE]| Self::from_random_bytes(bytes)).await + } + + pub fn from_random_bytes(bytes: [u8; UUID_SIZE]) -> Self { + Self(Builder::from_random_bytes(bytes).into_uuid()) + } + + pub fn max() -> Self { + Self(UuidImpl::max()) + } + + pub fn min() -> Self { + Self(UuidImpl::nil()) + } +} + +impl TryFrom<&str> for Uuid { + type Error = String; + + fn try_from(uuid: &str) -> Result { + let uuid = UuidImpl::parse_str(uuid) + .map_err(|_| format!("Failed to parse UUID from string: {}", uuid))?; + + Ok(Self(uuid)) + } +} + +impl ToString for Uuid { + fn to_string(&self) -> String { + self.0.to_string() + } +} + +impl CandidType for Uuid { + fn _ty() -> Type { + TypeInner::Text.into() + } + + fn idl_serialize(&self, serializer: S) -> Result<(), S::Error> + where + S: Serializer, + { + self.to_string().idl_serialize(serializer) + } +} + +impl Serialize for Uuid { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.to_string().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Uuid { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer).and_then(|uuid| { + Uuid::try_from(uuid.as_str()).map_err(|_| serde::de::Error::custom("Invalid UUID.")) + }) + } +} + +impl Storable for Uuid { + fn to_bytes(&self) -> std::borrow::Cow<[u8]> { + Cow::Borrowed(self.0.as_bytes()) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + Self(UuidImpl::from_bytes(bytes.into_owned().try_into().unwrap())) + } + + const BOUND: Bound = Bound::Bounded { + max_size: UUID_SIZE as u32, + is_fixed_size: true, + }; +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::*; + + #[rstest] + fn storable_impl() { + let uuid = uuid(); + + let serialized_uuid = uuid.to_bytes(); + let deserialized_uuid = Uuid::from_bytes(serialized_uuid); + + assert_eq!(deserialized_uuid, uuid); + } + + #[rstest] + fn try_from() { + let uuid = "e645cfd2-b365-4bda-bb64-535ffa050328"; + + let result = Uuid::try_from(uuid).unwrap(); + + assert_eq!(result.to_string(), uuid.to_string()); + } + + #[rstest] + fn try_from_invalid_uuid() { + let uuid_string = "not a uuid"; + + let result = Uuid::try_from(uuid_string).unwrap_err(); + + assert_eq!( + result, + format!("Failed to parse UUID from string: {}", uuid_string) + ); + } + + fn uuid() -> Uuid { + Uuid::try_from("36a1174f-b789-46e4-a5d6-ef8d38cd52b9").unwrap() + } +} diff --git a/apps/ssp_backend/src/repositories/user_repository.rs b/apps/ssp_backend/src/repositories/user_repository.rs new file mode 100644 index 0000000..3e89373 --- /dev/null +++ b/apps/ssp_backend/src/repositories/user_repository.rs @@ -0,0 +1,79 @@ +use std::cell::RefCell; + +use super::{ + init_user_db_id_index, init_user_sub_index, init_users, User, UserDbId, UserDbIdIndexMemory, + UserMemory, UserPrincipal, UserSub, UserSubIndexMemory, +}; + +pub struct UserState { + users: UserMemory, + user_sub_index: UserSubIndexMemory, + user_db_id_index: UserDbIdIndexMemory, +} + +impl Default for UserState { + fn default() -> Self { + Self { + users: init_users(), + user_sub_index: init_user_sub_index(), + user_db_id_index: init_user_db_id_index(), + } + } +} + +thread_local! { + static STATE: RefCell = RefCell::new(UserState::default()); +} + +#[derive(Default)] +pub struct UserRepository {} + +impl UserRepository { + pub fn get_user_by_principal(&self, user_principal: &UserPrincipal) -> Option { + STATE.with_borrow(|s| s.users.get(user_principal)) + } + + pub fn get_user_by_sub(&self, user_sub: &UserSub) -> Option<(UserPrincipal, User)> { + STATE.with_borrow(|s| { + s.user_sub_index.get(user_sub).and_then(|user_principal| { + s.users + .get(&user_principal) + .map(|user| (user_principal, user)) + }) + }) + } + + pub fn get_user_by_db_id(&self, db_id: &UserDbId) -> Option<(UserPrincipal, User)> { + STATE.with_borrow(|s| { + s.user_db_id_index.get(db_id).and_then(|user_principal| { + s.users + .get(&user_principal) + .map(|user| (user_principal, user)) + }) + }) + } + + pub fn create_user(&self, user_principal: UserPrincipal, user: User) -> Result<(), String> { + let user_sub = user.jwt_sub.clone(); + let db_id = user.db_id; + + if self.get_user_by_sub(&user_sub).is_some() { + return Err(format!("User with sub {} already exists", user_sub)); + } + + if self.get_user_by_db_id(&db_id).is_some() { + return Err(format!( + "User with database id {} already exists", + db_id.to_string() + )); + } + + STATE.with_borrow_mut(|s| { + s.users.insert(user_principal, user); + s.user_sub_index.insert(user_sub, user_principal); + s.user_db_id_index.insert(db_id, user_principal); + }); + + Ok(()) + } +} diff --git a/apps/ssp_backend/src/services/access_control_service.rs b/apps/ssp_backend/src/services/access_control_service.rs new file mode 100644 index 0000000..a10025e --- /dev/null +++ b/apps/ssp_backend/src/services/access_control_service.rs @@ -0,0 +1,66 @@ +use candid::Principal; +use ic_cdk::api::is_controller; + +use crate::repositories::{ConfigRepository, UserRepository}; + +#[derive(Default)] +pub struct AccessControlService { + user_repository: UserRepository, + config_repository: ConfigRepository, +} + +impl AccessControlService { + pub fn assert_principal_is_controller( + &self, + calling_principal: &Principal, + ) -> Result<(), String> { + if !is_controller(calling_principal) { + return Err("Caller is not a controller".to_string()); + } + + Ok(()) + } + + pub fn assert_principal_is_user(&self, calling_principal: &Principal) -> Result<(), String> { + if self + .user_repository + .get_user_by_principal(calling_principal) + .is_none() + { + return Err(format!( + "Caller {} is not a user", + calling_principal.to_text() + )); + } + + Ok(()) + } + + pub fn assert_principal_is_backend(&self, calling_principal: &Principal) -> Result<(), String> { + self.config_repository + .get_config() + .backend_principal + .ok_or_else(|| "Backend principal not set".to_string()) + .and_then(|backend_principal| { + if calling_principal.ne(&backend_principal) { + return Err("Caller is not the backend".to_string()); + } + + Ok(()) + }) + } + + pub fn assert_principal_is_user_or_backend<'a>( + &self, + calling_principal: &'a Principal, + ) -> Result, String> { + match self.assert_principal_is_backend(calling_principal) { + Ok(_) => Ok(None), + Err(_) => { + self.assert_principal_is_user(calling_principal) + .map_err(|_| "Caller is not the backend or a registered user".to_string())?; + Ok(Some(calling_principal)) + } + } + } +} diff --git a/apps/ssp_backend/src/services/certificate_service.rs b/apps/ssp_backend/src/services/certificate_service.rs new file mode 100644 index 0000000..9b72bf1 --- /dev/null +++ b/apps/ssp_backend/src/services/certificate_service.rs @@ -0,0 +1,163 @@ +use candid::Principal; +use ic_cdk::println; +use ssp_backend_types::{ + CreateCertificateRequest, CreateCertificateResponse, GetCertificateResponse, + GetUserCertificatesRequest, GetUserCertificatesResponse, ValidateRequest, +}; + +use crate::{ + mappings::{ + map_certificate_preview_with_id, map_certificate_with_id, map_create_certificate_response, + }, + repositories::{ + Certificate, CertificateId, CertificateRepository, DateTime, + UserCertificateWithCertification, UserDbId, UserRepository, + }, + system_api::get_date_time, +}; + +#[derive(Default)] +pub struct CertificateService { + certificate_repository: CertificateRepository, + user_repository: UserRepository, +} + +impl CertificateService { + pub fn get_certificate( + &self, + id: String, + only_user_principal: Option, + ) -> Result { + let id = CertificateId::try_from(id.as_str())?; + + let UserCertificateWithCertification { + certificate, + ic_certificate, + ic_certificate_witness, + } = self.certificate_repository.get_certificate(&id); + + match certificate { + Some(cert) => { + if let Some(p) = only_user_principal { + if p != cert.user_principal { + return Err("User can only access their own certificates".to_string()); + } + } + + Ok(GetCertificateResponse { + certificate: map_certificate_with_id(id, cert), + ic_certificate, + ic_certificate_witness, + }) + } + None => Err("Certificate not found".to_string()), + } + } + + pub fn get_user_certificates( + &self, + request: GetUserCertificatesRequest, + only_user_principal: Option, + ) -> Result { + request.validate()?; + + let maybe_user_db_id = match request.user_db_id { + Some(id) => Some(UserDbId::try_from(id.as_str())?), + None => None, + }; + let maybe_user_principal = request.user_principal.or_else(|| { + // at this point, the user_db_id has a value because of the validation, + // which requires either user_db_id or user_principal to be provided + let user_db_id = maybe_user_db_id.unwrap(); + + self.user_repository + .get_user_by_db_id(&user_db_id) + .map(|(p, _)| p) + }); + + let mut certificates = vec![]; + if let Some(user_principal) = maybe_user_principal { + if let Some(p) = only_user_principal { + if p != user_principal { + return Err("User can only access their own certificates".to_string()); + } + } + + certificates = self + .certificate_repository + .get_certificates_by_user_principal(&user_principal)?; + } + + // try getting the certificates by managed user id + if certificates.is_empty() { + if let Some(user_db_id) = maybe_user_db_id { + certificates = self + .certificate_repository + .get_certificates_by_managed_user_id(&user_db_id)?; + if let Some(p) = only_user_principal { + certificates.retain(|(_, cert)| cert.user_principal == p); + } + } + } + + Ok(GetUserCertificatesResponse { + certificates: certificates + .into_iter() + .map(|(id, certificate)| map_certificate_preview_with_id(id, certificate)) + .collect(), + }) + } + + pub async fn create_certificate( + &self, + request: CreateCertificateRequest, + calling_user_principal: Option, + ) -> Result { + request.validate()?; + + let user_principal = match calling_user_principal { + Some(principal) => principal, + None => { + let user_db_id = UserDbId::try_from(request.user_db_id.as_str())?; + self.user_repository + .get_user_by_db_id(&user_db_id) + .ok_or_else(|| { + format!( + "User with database id {} does not exist", + user_db_id.to_string() + ) + })? + .0 + } + }; + + let date_time = get_date_time()?; + + let certificate = Certificate { + user_principal, + created_at: DateTime::new(date_time)?, + content: request.content.try_into()?, + managed_user_id: match request.managed_user_db_id { + Some(id) => Some(UserDbId::try_from(id.as_str())?), + None => None, + }, + }; + + let id = self + .certificate_repository + .create_certificate(certificate) + .await?; + + println!( + "Created certificate for user {} with id: {}", + user_principal.to_text(), + id.to_string() + ); + + Ok(map_create_certificate_response(id)) + } + + pub fn certify_all_certificates(&self) { + self.certificate_repository.certify_all_certificates(); + } +} diff --git a/apps/ssp_backend/src/services/config_service.rs b/apps/ssp_backend/src/services/config_service.rs new file mode 100644 index 0000000..320946e --- /dev/null +++ b/apps/ssp_backend/src/services/config_service.rs @@ -0,0 +1,26 @@ +use candid::Principal; + +use crate::repositories::{Config, ConfigRepository}; + +#[derive(Default)] +pub struct ConfigService { + config_repository: ConfigRepository, +} + +impl ConfigService { + pub fn set_backend_principal(&self, backend_principal: Principal) -> Result<(), String> { + if backend_principal == Principal::anonymous() { + return Err("Backend principal cannot be anonymous".to_string()); + } + + let mut config = self.config_repository.get_config(); + + config.backend_principal = Some(backend_principal); + + self.config_repository.set_config(config) + } + + pub fn get_config(&self) -> Config { + self.config_repository.get_config() + } +} diff --git a/apps/ssp_backend/src/services/delegation_service.rs b/apps/ssp_backend/src/services/delegation_service.rs new file mode 100644 index 0000000..a6a8f27 --- /dev/null +++ b/apps/ssp_backend/src/services/delegation_service.rs @@ -0,0 +1,265 @@ +mod utils; + +use candid::Principal; +use canister_sig_util::{hash_bytes, signature_map::LABEL_SIG}; +use ic_cdk::{ + api::{ + management_canister::http_request::{ + http_request, CanisterHttpRequestArgument, HttpMethod, HttpResponse, TransformArgs, + TransformContext, + }, + set_certified_data, + }, + print, trap, +}; +use ic_certification::labeled_hash; +use ic_certification::Hash; +use jsonwebtoken_rustcrypto::Algorithm; +use serde_bytes::ByteBuf; +use ssp_backend_types::{ + Auth0JWKSet, Delegation, GetDelegationResponse, PrepareDelegationResponse, SessionKey, + SignedDelegation, Timestamp, +}; + +use crate::repositories::{ + decode_jwt, DelegationRepository, IdToken, User, UserDbId, UserRepository, UserSub, + AUTH0_ISSUER, EMPTY_SALT, +}; + +use self::utils::{delegation_signature_msg_hash, der_encode_canister_sig_key, random_salt}; + +const SUBNET_SIZE: u128 = 13; +// the response should be around 3KB, so we set a limit of 10KB +const MAX_RESPONSE_BYTES: u128 = 10_000; +// there's no body in the request, so we can set a low value +const REQUEST_BYTES: u128 = 100; + +#[derive(Default)] +pub struct DelegationService { + delegation_repository: DelegationRepository, + user_repository: UserRepository, +} + +impl DelegationService { + pub async fn ensure_salt_initialized(&self) { + let salt = self.delegation_repository.get_salt(); + if salt == EMPTY_SALT { + let salt = random_salt().await; + self.delegation_repository.set_salt(salt); + } + } + + pub async fn fetch_and_store_jwks(&self) -> Result<(), String> { + // Formula from https://internetcomputer.org/docs/current/developer-docs/gas-cost#special-features. + // Parameters calculated with https://github.com/domwoe/HTTPS-Outcalls-Calculator. + let cycles: u128 = (3_000_000 + (60_000 * SUBNET_SIZE)) * SUBNET_SIZE + + ((400 * SUBNET_SIZE) * REQUEST_BYTES) + + ((800 * SUBNET_SIZE) * MAX_RESPONSE_BYTES); + + let (res,) = http_request( + CanisterHttpRequestArgument { + url: format!("{AUTH0_ISSUER}.well-known/jwks.json"), + method: HttpMethod::GET, + headers: vec![], + body: None, + max_response_bytes: Some(MAX_RESPONSE_BYTES.try_into().unwrap()), + transform: Some(TransformContext::from_name( + "transform_jwks_response".to_string(), + vec![], + )), + }, + cycles, + ) + .await + .map_err(|e| format!("Error fetching JWKS: {:?}", e))?; + + let jwks: Auth0JWKSet = serde_json::from_slice(&res.body) + .map_err(|e| format!("Error parsing JWKS: {:?}", e))?; + self.delegation_repository.set_jwks(jwks.clone()); + + print(format!( + "Fetched JWKS. JSON Web Keys available: {}", + jwks.keys.len() + )); + + Ok(()) + } + + pub fn transform_jwks_response(&self, args: TransformArgs) -> HttpResponse { + let raw_response = args.response; + // We just need to agree on the body content and content type. + HttpResponse { + status: 200u8.into(), + body: raw_response.body, + headers: raw_response + .headers + .into_iter() + .filter(|header| header.name.to_lowercase() == "content-type") + .collect(), + } + } + + pub async fn prepare_delegation( + &self, + session_principal: Principal, + jwt: String, + ) -> Result { + let (token, session_key) = match self.check_authorization(session_principal, jwt) { + Ok(res) => res, + Err(e) => { + trap(&e); + } + }; + + let sub = token.claims.clone().sub; + let db_id = match token.claims.clone().user_id() { + Some(id) => UserDbId::try_from(id.as_str()).unwrap(), + None => return Err("User ID not found in hasura claims".to_string()), + }; + let expiration = token.claims.expiration_timestamp_ns(); + + self.ensure_salt_initialized().await; + + let user_key = self.create_delegation(&sub, session_key, expiration); + let user_principal = self.principal_from_sub(&sub); + + let user = User::new(sub, db_id.to_string().as_str()).unwrap(); + if self + .user_repository + .get_user_by_principal(&user_principal) + .is_none() + { + self.user_repository + .create_user(user_principal, user) + .unwrap(); + } + + Ok(PrepareDelegationResponse { + user_key, + expiration, + }) + } + + pub fn get_delegation( + &self, + session_principal: Principal, + jwt: String, + expiration: Timestamp, + ) -> GetDelegationResponse { + let (token, session_key) = match self.check_authorization(session_principal, jwt) { + Ok(res) => res, + Err(e) => { + trap(&e); + } + }; + + let sub = &token.claims.sub; + self.load_delegation(sub, session_key, expiration) + } + + pub fn get_jwks(&self) -> Option { + self.delegation_repository.get_jwks() + } + + pub fn set_jwks(&self, jwks: Auth0JWKSet) { + // add an extra layer of security: + // we can only set the jwks once + if self.get_jwks().is_some() { + trap("JWKS already set. Call sync_jwks to fetch the JWKS from the auth provider"); + } + + self.delegation_repository.set_jwks(jwks) + } + + fn check_authorization( + &self, + caller: Principal, + jwt: String, + ) -> Result<(IdToken, SessionKey), String> { + let jwks = self.delegation_repository.get_jwks(); + let token = + decode_jwt(&jwt, Algorithm::RS256, jwks.as_ref()).map_err(|e| format!("{:?}", e))?; + + token.claims.validate().map_err(|e| format!("{:?}", e))?; + + let nonce = { + let nonce = hex::decode(&token.claims.nonce).map_err(|e| format!("{:?}", e))?; + ByteBuf::from(nonce) + }; + let token_principal = Principal::self_authenticating(&nonce); + if caller != token_principal { + return Err("caller and token principal mismatch".to_string()); + } + + Ok((token, nonce)) + } + + fn create_delegation( + &self, + user_sub: &UserSub, + session_key: SessionKey, + expiration: Timestamp, + ) -> ByteBuf { + let seed = self.calculate_seed(user_sub); + + let msg_hash = delegation_signature_msg_hash(&session_key, expiration); + self.delegation_repository + .add_delegation_signature(&seed, msg_hash); + + self.update_root_hash(); + + ByteBuf::from(der_encode_canister_sig_key(seed.to_vec())) + } + + fn update_root_hash(&self) { + let root_hash = self.delegation_repository.get_sigs_root_hash(); + let prefixed_root_hash = labeled_hash(LABEL_SIG, &root_hash); + set_certified_data(&prefixed_root_hash[..]); + } + + fn load_delegation( + &self, + user_sub: &UserSub, + session_key: SessionKey, + expiration: Timestamp, + ) -> GetDelegationResponse { + let message_hash = delegation_signature_msg_hash(&session_key, expiration); + + let seed = self.calculate_seed(user_sub); + + match self + .delegation_repository + .get_signature(&seed, message_hash) + { + Ok(signature) => GetDelegationResponse::SignedDelegation(SignedDelegation { + delegation: Delegation { + pubkey: session_key, + expiration, + targets: None, + }, + signature: ByteBuf::from(signature), + }), + Err(_) => GetDelegationResponse::NoSuchDelegation, + } + } + + fn principal_from_sub(&self, user_sub: &UserSub) -> Principal { + let seed = self.calculate_seed(user_sub); + let public_key = der_encode_canister_sig_key(seed.to_vec()); + Principal::self_authenticating(public_key) + } + + fn calculate_seed(&self, user_sub: &UserSub) -> Hash { + let salt = self.delegation_repository.get_salt(); + + let mut blob: Vec = vec![]; + blob.push(salt.len() as u8); + blob.extend_from_slice(&salt); + + let user_sub_blob = user_sub.bytes(); + blob.push(user_sub_blob.len() as u8); + blob.extend(user_sub_blob); + + hash_bytes(blob) + } +} diff --git a/apps/ssp_backend/src/services/delegation_service/utils.rs b/apps/ssp_backend/src/services/delegation_service/utils.rs new file mode 100644 index 0000000..7e80964 --- /dev/null +++ b/apps/ssp_backend/src/services/delegation_service/utils.rs @@ -0,0 +1,31 @@ +use canister_sig_util::{delegation_signature_msg, hash_bytes, CanisterSigPublicKey}; +use ic_cdk::{api::management_canister::main::raw_rand, id, trap}; +use ic_certification::Hash; +use ssp_backend_types::{PublicKey, Timestamp}; + +use crate::repositories::Salt; + +pub(super) fn delegation_signature_msg_hash(pubkey: &PublicKey, expiration: Timestamp) -> Hash { + let msg = delegation_signature_msg(pubkey, expiration, None); + hash_bytes(msg) +} + +pub(super) fn der_encode_canister_sig_key(seed: Vec) -> Vec { + let my_canister_id = id(); + CanisterSigPublicKey::new(my_canister_id, seed).to_der() +} + +/// Calls raw rand to retrieve a random salt (32 bytes). +pub(super) async fn random_salt() -> Salt { + let res: Vec = match raw_rand().await { + Ok((res,)) => res, + Err((_, err)) => trap(&format!("failed to get salt: {err}")), + }; + let salt: Salt = res[..].try_into().unwrap_or_else(|_| { + trap(&format!( + "expected raw randomness to be of length 32, got {}", + res.len() + )); + }); + salt +} diff --git a/apps/ssp_backend/src/services/mod.rs b/apps/ssp_backend/src/services/mod.rs new file mode 100644 index 0000000..1af2886 --- /dev/null +++ b/apps/ssp_backend/src/services/mod.rs @@ -0,0 +1,11 @@ +mod access_control_service; +mod certificate_service; +mod config_service; +mod delegation_service; +mod user_service; + +pub use access_control_service::*; +pub use certificate_service::*; +pub use config_service::*; +pub use delegation_service::*; +pub use user_service::*; diff --git a/apps/ssp_backend/src/services/user_service.rs b/apps/ssp_backend/src/services/user_service.rs new file mode 100644 index 0000000..d620230 --- /dev/null +++ b/apps/ssp_backend/src/services/user_service.rs @@ -0,0 +1,12 @@ +use crate::repositories::{User, UserPrincipal, UserRepository}; + +#[derive(Default)] +pub struct UserService { + user_repository: UserRepository, +} + +impl UserService { + pub fn get_user(&self, user_principal: &UserPrincipal) -> Option { + self.user_repository.get_user_by_principal(user_principal) + } +} diff --git a/apps/ssp_backend/src/system_api/mod.rs b/apps/ssp_backend/src/system_api/mod.rs new file mode 100644 index 0000000..81c5585 --- /dev/null +++ b/apps/ssp_backend/src/system_api/mod.rs @@ -0,0 +1,5 @@ +mod rand; +mod time; + +pub use rand::*; +pub use time::*; diff --git a/apps/ssp_backend/src/system_api/rand.rs b/apps/ssp_backend/src/system_api/rand.rs new file mode 100644 index 0000000..efc47c4 --- /dev/null +++ b/apps/ssp_backend/src/system_api/rand.rs @@ -0,0 +1,63 @@ +use rand::prelude::*; +use rand_chacha::ChaCha20Rng; +use std::cell::RefCell; + +#[cfg(target_family = "wasm")] +use ic_cdk::api::management_canister::main::raw_rand; + +thread_local! { + static RNG: RefCell> = const { RefCell::new(None) }; +} + +async fn with_rng(cb: impl FnOnce(&mut ChaCha20Rng) -> T) -> Result { + let is_init = RNG.with_borrow(|rng| rng.is_some()); + + if !is_init { + let seed = get_seed().await?; + + let rng = ChaCha20Rng::from_seed(seed); + RNG.with(|option_rng| { + option_rng.borrow_mut().get_or_insert(rng); + }); + } + + RNG.with_borrow_mut(|rng| { + let rng = rng + .as_mut() + .ok_or_else(|| "Failed to initialize random number generator".to_string())?; + + Ok(cb(rng)) + }) +} + +async fn get_seed() -> Result<[u8; 32], String> { + #[cfg(target_family = "wasm")] + { + let (seed,) = raw_rand().await.map_err(|(code, msg)| { + format!("System API call to `raw_rand` failed: ({:?}) {}", code, msg) + })?; + + seed.try_into().map_err(|err| { + format!( + "System API call to `raw_rand` did not return 32 bytes: ({:?})", + err + ) + }) + } + + // fallback seed for non-wasm targets, e.g. unit tests + #[cfg(not(target_family = "wasm"))] + Ok([0u8; 32]) +} + +pub async fn with_random_bytes( + cb: impl FnOnce([u8; N]) -> T, +) -> Result { + with_rng(|rng| { + let mut bytes = [0u8; N]; + rng.fill_bytes(&mut bytes); + + cb(bytes) + }) + .await +} diff --git a/apps/ssp_backend/src/system_api/time.rs b/apps/ssp_backend/src/system_api/time.rs new file mode 100644 index 0000000..e461264 --- /dev/null +++ b/apps/ssp_backend/src/system_api/time.rs @@ -0,0 +1,32 @@ +pub const NANOS_IN_SECONDS: u64 = 1_000_000_000; + +fn unix_timestamp_ns() -> u64 { + #[cfg(target_family = "wasm")] + { + ic_cdk::api::time() + } + + #[cfg(not(target_family = "wasm"))] + { + // fixed point in time: 2024-01-01T00:00:00Z + 1704063600000000000 + } +} + +/// Returns the current unix timestamp in seconds +pub fn unix_timestamp() -> u64 { + unix_timestamp_ns() / NANOS_IN_SECONDS +} + +pub fn get_date_time() -> Result, String> { + let unix_ts = unix_timestamp(); + let timestamp_s: i64 = unix_ts.try_into().map_err(|_| { + format!( + "Failed to convert timestamp {} from nanoseconds to seconds", + unix_ts + ) + })?; + + chrono::DateTime::from_timestamp(timestamp_s, 0) + .ok_or_else(|| format!("Failed to convert timestamp {} to DateTime", timestamp_s)) +} diff --git a/apps/ssp_backend/src/utils.rs b/apps/ssp_backend/src/utils.rs new file mode 100644 index 0000000..25d4c4f --- /dev/null +++ b/apps/ssp_backend/src/utils.rs @@ -0,0 +1,12 @@ +use serde::Serialize; +use serde_cbor::Serializer; + +pub fn cbor_serialize(value: &T) -> Result, String> { + let mut data = vec![]; + let mut serializer = Serializer::new(&mut data); + serializer.self_describe().map_err(|e| e.to_string())?; + value + .serialize(&mut serializer) + .map_err(|e| e.to_string())?; + Ok(data) +} diff --git a/apps/ssp_backend/ssp_backend.did b/apps/ssp_backend/ssp_backend.did new file mode 100644 index 0000000..d9f37fc --- /dev/null +++ b/apps/ssp_backend/ssp_backend.did @@ -0,0 +1,129 @@ +type PublicKey = blob; +type UserKey = PublicKey; +type Timestamp = nat64; +type Signature = blob; + +type PrepareDelegationResponse = record { + user_key : UserKey; + expiration : Timestamp; +}; + +type Delegation = record { + pubkey : PublicKey; + expiration : Timestamp; + targets : opt vec principal; +}; + +type SignedDelegation = record { + delegation : Delegation; + signature : Signature; +}; + +type GetDelegationResponse = variant { + signed_delegation : SignedDelegation; + no_such_delegation; +}; + +type Auth0JWK = record { + kty : text; + use : text; + n : text; + e : text; + kid : text; + x5t : text; + x5c : vec text; + alg : text; +}; + +type Auth0JWKS = record { + keys : vec Auth0JWK; +}; + +type Config = record { + backend_principal : opt principal; +}; + +type User = record { + sub : text; + db_id : text; + created_at : text; +}; + +type CertificateContent = record { + name : text; + issued_at : text; + sport_category : text; + notes : opt text; + file_uri : opt text; + external_id : opt text; + issuer_full_name : opt text; + issuer_club_name : opt text; +}; + +type Certificate = record { + user_principal : principal; + created_at : text; + content : CertificateContent; + managed_user_id : opt text; +}; + +type CreateCertificateContentRequest = record { + name : text; + issued_at : Timestamp; + sport_category : text; + notes : opt text; + file_uri : opt text; + external_id : opt text; + issuer_full_name : opt text; + issuer_club_name : opt text; +}; + +type CreateCertificateRequest = record { + user_db_id : text; + content : CreateCertificateContentRequest; + managed_user_db_id : opt text; +}; + +type CreateCertificateResponse = record { + id : text; +}; + +type GetUserCertificatesRequest = record { + user_principal : opt principal; + user_db_id : opt text; +}; + +type CertificatePreviewWithId = record { + id : text; + name : text; +}; + +type GetUserCertificatesResponse = record { + certificates : vec CertificatePreviewWithId; +}; + +type CertificateWithId = record { + id : text; + certificate_cbor_hex : text; +}; + +type GetCertificateResponse = record { + certificate : CertificateWithId; + ic_certificate : blob; + ic_certificate_witness : blob; +}; + +service : { + "prepare_delegation" : (text) -> (PrepareDelegationResponse); + "get_delegation" : (text, Timestamp) -> (GetDelegationResponse) query; + "sync_jwks" : () -> (); + "set_jwks" : (Auth0JWKS) -> (); + "get_jwks" : () -> (opt Auth0JWKS) query; + "set_backend_principal" : (principal) -> (); + "get_config" : () -> (Config) query; + + "get_my_user" : () -> (User) query; + "create_certificate" : (CreateCertificateRequest) -> (CreateCertificateResponse); + "get_user_certificates" : (GetUserCertificatesRequest) -> (GetUserCertificatesResponse) query; + "get_certificate" : (text) -> (GetCertificateResponse) query; +}; diff --git a/apps/ssp_backend/tests/authenticated.rs b/apps/ssp_backend/tests/authenticated.rs new file mode 100644 index 0000000..33a7912 --- /dev/null +++ b/apps/ssp_backend/tests/authenticated.rs @@ -0,0 +1,172 @@ +pub mod common; + +use candid::Principal; +use ic_agent::Identity; +use jwt_simple::prelude::*; +use ssp_backend_types::{GetDelegationResponse, PrepareDelegationResponse, User}; + +use common::{ + auth_provider::{create_jwt, initialize_auth_provider}, + canister::{ + extract_trap_message, get_delegation, get_my_user, initialize_canister, prepare_delegation, + }, + date_time::date_time_str_from_canister_time, + identity::{delegated_identity_from_delegation, generate_random_identity, pk_to_hex}, + test_env::{create_test_env, upgrade_canister}, +}; + +/// Same as on Auth0 +const JWT_VALID_FOR_HOURS: u64 = 10; + +const TEST_USER_SUB: &str = "test_sub"; +const TEST_USER_DB_ID: &str = "fafc11f5-c784-4cbe-9fbf-207889afd519"; + +#[test] +fn test_get_my_user_no_user() { + let env = create_test_env(); + let (_, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let identity = generate_random_identity(); + let res = get_my_user(&env, identity.sender().unwrap()).unwrap_err(); + + assert!(extract_trap_message(res).contains("No user found")); +} + +#[test] +fn test_get_my_user() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_identity.public_key().unwrap()), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let PrepareDelegationResponse { + expiration, + user_key, + } = prepare_delegation(&env, session_principal, jwt.clone()).unwrap(); + let signed_delegation = match get_delegation(&env, session_principal, jwt, expiration).unwrap() + { + GetDelegationResponse::SignedDelegation(delegation) => delegation, + _ => panic!("expected GetDelegationResponse::SignedDelegation"), + }; + + // construct the delegated identity + let user_identity = + delegated_identity_from_delegation(user_key, session_identity, signed_delegation); + let user_principal = user_identity.sender().unwrap(); + + let res = get_my_user(&env, user_principal).unwrap(); + + assert_eq!( + res, + User { + sub: TEST_USER_SUB.to_string(), + created_at: date_time_str_from_canister_time(env.get_canister_time()), + db_id: TEST_USER_DB_ID.to_string(), + } + ); +} + +#[test] +fn test_get_my_user_wrong_identity() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_identity.public_key().unwrap()), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let PrepareDelegationResponse { expiration, .. } = + prepare_delegation(&env, session_principal, jwt.clone()).unwrap(); + get_delegation(&env, session_principal, jwt, expiration).unwrap(); + + // use the session identity to call the method + let res = get_my_user(&env, session_principal).unwrap_err(); + assert!(extract_trap_message(res).contains("No user found")); + + // use another identity to call the method + let wrong_identity = generate_random_identity(); + let res = get_my_user(&env, wrong_identity.sender().unwrap()).unwrap_err(); + assert!(extract_trap_message(res).contains("No user found")); +} + +#[test] +fn test_get_my_user_anonymous() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_identity.public_key().unwrap()), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let PrepareDelegationResponse { expiration, .. } = + prepare_delegation(&env, session_principal, jwt.clone()).unwrap(); + get_delegation(&env, session_principal, jwt, expiration).unwrap(); + + let res = get_my_user(&env, Principal::anonymous()).unwrap_err(); + assert!(extract_trap_message(res).contains("No user found")); +} + +#[test] +fn test_get_my_user_across_upgrades() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks.clone()); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_identity.public_key().unwrap()), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let PrepareDelegationResponse { + expiration, + user_key, + } = prepare_delegation(&env, session_principal, jwt.clone()).unwrap(); + let signed_delegation = match get_delegation(&env, session_principal, jwt, expiration).unwrap() + { + GetDelegationResponse::SignedDelegation(delegation) => delegation, + _ => panic!("expected GetDelegationResponse::SignedDelegation"), + }; + + let user_identity = + delegated_identity_from_delegation(user_key, session_identity, signed_delegation); + let user_principal = user_identity.sender().unwrap(); + + let res_before_upgrade = get_my_user(&env, user_principal).unwrap(); + + // upgrade the canister + upgrade_canister(&env); + initialize_canister(&env, jwks); + + let res_after_upgrade = get_my_user(&env, user_principal).unwrap(); + + assert_eq!(res_before_upgrade, res_after_upgrade); +} diff --git a/apps/ssp_backend/tests/certificate.rs b/apps/ssp_backend/tests/certificate.rs new file mode 100644 index 0000000..9333512 --- /dev/null +++ b/apps/ssp_backend/tests/certificate.rs @@ -0,0 +1,844 @@ +pub mod common; + +use core::str; +use std::time::{SystemTime, UNIX_EPOCH}; + +use candid::Principal; +use common::{ + auth_provider::{create_jwt, initialize_auth_provider}, + canister::{ + create_certificate, extract_trap_message, get_certificate, get_delegation, + get_user_certificates, initialize_canister, prepare_delegation, set_backend_principal, + }, + identity::{delegated_identity_from_delegation, generate_random_identity, pk_to_hex}, + test_env::{self, upgrade_canister, TestEnv}, +}; +use ic_agent::{hash_tree::SubtreeLookupResult, identity::DelegatedIdentity, Identity}; +use ic_certificate_verification::VerifyCertificate; +use ic_certification::{ + leaf_hash, Certificate as IcCertificate, HashTree, HashTreeNode, LookupResult, +}; +use jwt_simple::prelude::*; +use ssp_backend_types::{ + Certificate, CertificateWithId, CreateCertificateContentRequest, CreateCertificateRequest, + GetDelegationResponse, GetUserCertificatesRequest, PrepareDelegationResponse, + MAX_EXTERNAL_ID_CHARS_COUNT, MAX_FILE_BYTES_SIZE, MAX_ISSUER_CLUB_NAME_CHARS_COUNT, + MAX_ISSUER_FULL_NAME_CHARS_COUNT, MAX_NAME_CHARS_COUNT, MAX_NOTES_CHARS_COUNT, + MAX_SPORT_CATEGORY_CHARS_COUNT, +}; +use uuid::Uuid; + +const JWT_VALID_FOR_HOURS: u64 = 10; + +const TEST_USER_SUB: &str = "test_sub"; +const TEST_USER_DB_ID: &str = "96b51c08-9846-40f2-8f37-a1e4421e2ba8"; + +const MAX_IC_CERT_TIME_OFFSET_NS: u128 = 300_000_000_000; // 5 min + +fn certificate_content_request() -> CreateCertificateContentRequest { + CreateCertificateContentRequest { + name: "Test certificate".to_string(), + issued_at: 1704063600000000, // 2024-01-01 00:00:00 in microseconds + sport_category: "Swimming".to_string(), + notes: Some("Test notes".to_string()), + file_uri: Some( + "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" + .to_string(), + ), + external_id: Some("123456".to_string()), + issuer_full_name: Some("John Doe".to_string()), + issuer_club_name: Some("Swimming club".to_string()), + } +} + +fn decode_certificate(certificate_cbor_hex: &str) -> Certificate { + let certificate_bytes = hex::decode(certificate_cbor_hex).unwrap(); + serde_cbor::from_slice(&certificate_bytes).unwrap() +} + +fn setup_config(env: &TestEnv) -> Principal { + let sender = env.controller(); + let backend_principal = generate_random_identity().sender().unwrap(); + + set_backend_principal(env, sender, backend_principal).unwrap(); + + backend_principal +} + +fn create_user( + env: &TestEnv, + auth_provider_key_pair: &RS256KeyPair, + user_sub: &str, + db_id: &str, +) -> DelegatedIdentity { + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let (jwt, _) = create_jwt( + auth_provider_key_pair, + user_sub, + &pk_to_hex(&session_identity.public_key().unwrap()), + Some(db_id), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let PrepareDelegationResponse { + expiration, + user_key, + } = prepare_delegation(env, session_principal, jwt.clone()).unwrap(); + let signed_delegation = match get_delegation(env, session_principal, jwt, expiration).unwrap() { + GetDelegationResponse::SignedDelegation(delegation) => delegation, + _ => panic!("expected GetDelegationResponse::SignedDelegation"), + }; + + // construct the delegated identity + delegated_identity_from_delegation(user_key, session_identity, signed_delegation) +} + +fn setup_user(env: &TestEnv, user_sub: &str, db_id: &str) -> DelegatedIdentity { + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(env, jwks); + + create_user(env, &auth_provider_key_pair, user_sub, db_id) +} + +#[test] +fn test_create_certificate_anonymous() { + let env = test_env::create_test_env(); + setup_config(&env); + setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + + let sender = Principal::anonymous(); + + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: certificate_content_request(), + managed_user_db_id: None, + }; + + let res = create_certificate(&env, sender, request).unwrap_err(); + + assert!(extract_trap_message(res).contains("Caller is not the backend or a registered user")); +} + +#[test] +fn test_create_certificate_user() { + let env = test_env::create_test_env(); + setup_config(&env); + let user_identity = setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + + let user_principal = user_identity.sender().unwrap(); + + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: certificate_content_request(), + managed_user_db_id: None, + }; + + let res = create_certificate(&env, user_principal, request).unwrap(); + let res_certificate = get_certificate(&env, user_principal, res.id).unwrap(); + assert_eq!( + decode_certificate(&res_certificate.certificate.certificate_cbor_hex).user_principal, + user_principal + ); + + // check that the certificate owner is overwritten if another id is provided + let request = CreateCertificateRequest { + user_db_id: "83163621-9085-4e12-88bb-0b9aee290420".to_string(), + content: certificate_content_request(), + managed_user_db_id: None, + }; + let res_another_user = create_certificate(&env, user_principal, request).unwrap(); + let res_certificate_another_user = + get_certificate(&env, user_principal, res_another_user.id).unwrap(); + assert_eq!( + decode_certificate( + &res_certificate_another_user + .certificate + .certificate_cbor_hex + ) + .user_principal, + user_principal + ); +} + +#[test] +fn test_create_certificate() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + let user_identity = setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + let user_principal = user_identity.sender().unwrap(); + + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: certificate_content_request(), + managed_user_db_id: None, + }; + + let res = create_certificate(&env, backend_principal, request).unwrap(); + let res_certificate = get_certificate(&env, backend_principal, res.id).unwrap(); + assert_eq!( + decode_certificate(&res_certificate.certificate.certificate_cbor_hex).user_principal, + user_principal + ); +} + +#[test] +fn test_create_certificate_no_user() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: certificate_content_request(), + managed_user_db_id: None, + }; + + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + + assert!(extract_trap_message(res).contains(&format!( + "User with database id {TEST_USER_DB_ID} does not exist" + ))); +} + +#[test] +fn test_create_certificate_invalid_request() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + + // empty name + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + name: "".to_string(), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains("Title cannot be empty.")); + + // too long name + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + name: "a".repeat(MAX_NAME_CHARS_COUNT + 1), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains(&format!( + "Title cannot be longer than {} characters.", + MAX_NAME_CHARS_COUNT + ))); + + // empty sport category + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + sport_category: "".to_string(), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains("Sport category cannot be empty.")); + + // too long sport category + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + sport_category: "a".repeat(MAX_SPORT_CATEGORY_CHARS_COUNT + 1), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains(&format!( + "Sport category cannot be longer than {} characters.", + MAX_SPORT_CATEGORY_CHARS_COUNT + ))); + + // too long notes + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + notes: Some("a".repeat(MAX_NOTES_CHARS_COUNT + 1)), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains(&format!( + "Notes cannot be longer than {} characters.", + MAX_NOTES_CHARS_COUNT + ))); + + // too long file bytes + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + file_uri: Some( + str::from_utf8(&vec![1; MAX_FILE_BYTES_SIZE + 1]) + .unwrap() + .to_string(), + ), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains(&format!( + "File bytes cannot be longer than {} bytes.", + MAX_FILE_BYTES_SIZE + ))); + + // too long external id + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + external_id: Some("a".repeat(MAX_EXTERNAL_ID_CHARS_COUNT + 1)), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains(&format!( + "External ID cannot be longer than {} characters.", + MAX_EXTERNAL_ID_CHARS_COUNT + ))); + + // too long issuer full name + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + issuer_full_name: Some("a".repeat(MAX_ISSUER_FULL_NAME_CHARS_COUNT + 1)), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains(&format!( + "Issuer full name cannot be longer than {} characters.", + MAX_ISSUER_FULL_NAME_CHARS_COUNT + ))); + + // too long issuer club name + let request = CreateCertificateRequest { + user_db_id: TEST_USER_DB_ID.to_string(), + content: CreateCertificateContentRequest { + issuer_club_name: Some("a".repeat(MAX_ISSUER_CLUB_NAME_CHARS_COUNT + 1)), + ..certificate_content_request() + }, + managed_user_db_id: None, + }; + let res = create_certificate(&env, backend_principal, request).unwrap_err(); + assert!(extract_trap_message(res).contains(&format!( + "Issuer club name cannot be longer than {} characters.", + MAX_ISSUER_CLUB_NAME_CHARS_COUNT + ))); +} + +fn create_test_certificate( + env: &TestEnv, + backend_principal: Principal, + user_db_id: String, +) -> (String, String) { + let content = certificate_content_request(); + let request = CreateCertificateRequest { + user_db_id, + content: content.clone(), + managed_user_db_id: None, + }; + + let res = create_certificate(env, backend_principal, request).unwrap(); + (res.id, content.name) +} + +fn create_test_certificate_for_managed_user( + env: &TestEnv, + backend_principal: Principal, + user_db_id: String, + managed_user_db_id: String, +) -> (String, String) { + let content = certificate_content_request(); + let request = CreateCertificateRequest { + user_db_id, + content: content.clone(), + managed_user_db_id: Some(managed_user_db_id), + }; + + let res = create_certificate(env, backend_principal, request).unwrap(); + (res.id, content.name) +} + +#[test] +fn test_get_user_certificates_invalid_request() { + let env = test_env::create_test_env(); + let user_identity = setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + let user_principal = user_identity.sender().unwrap(); + + // empty request + let request = GetUserCertificatesRequest { + user_principal: None, + user_db_id: None, + }; + let res = get_user_certificates(&env, user_principal, request).unwrap_err(); + assert!( + extract_trap_message(res).contains("Either user_principal or user_db_id must be provided.") + ); + + // both user_principal and user_db_id are provided + let request = GetUserCertificatesRequest { + user_principal: Some(user_principal), + user_db_id: Some(TEST_USER_DB_ID.to_string()), + }; + let res = get_user_certificates(&env, user_principal, request).unwrap_err(); + assert!(extract_trap_message(res) + .contains("Only one of user_principal or user_db_id can be provided.")); +} + +#[test] +fn test_get_user_certificates_not_authorized() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + let user_identity = setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + let user_principal = user_identity.sender().unwrap(); + + const TEST_CERTIFICATES_COUNT: usize = 10; + for _ in 0..TEST_CERTIFICATES_COUNT { + create_test_certificate(&env, backend_principal, TEST_USER_DB_ID.to_string()); + } + + for sender in [ + generate_random_identity().sender().unwrap(), + Principal::anonymous(), + ] { + let request = GetUserCertificatesRequest { + user_principal: Some(user_principal), + user_db_id: None, + }; + let res = get_user_certificates(&env, sender, request).unwrap_err(); + assert!( + extract_trap_message(res).contains("Caller is not the backend or a registered user") + ); + } +} + +#[test] +fn test_get_user_certificates() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + let user_identity = setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + let user_principal = user_identity.sender().unwrap(); + + const TEST_CERTIFICATES_COUNT: usize = 10; + let mut created_certificates_previews = vec![]; + for _ in 0..TEST_CERTIFICATES_COUNT { + let res = create_test_certificate(&env, backend_principal, TEST_USER_DB_ID.to_string()); + created_certificates_previews.push(res); + } + + let test = |calling_principal: Principal| { + // by user principal + let request = GetUserCertificatesRequest { + user_principal: Some(user_principal), + user_db_id: None, + }; + let res_by_principal = get_user_certificates(&env, calling_principal, request).unwrap(); + assert_eq!(res_by_principal.certificates.len(), TEST_CERTIFICATES_COUNT); + // check that all certificates are present + for (id, name) in created_certificates_previews.iter() { + let certificate = res_by_principal + .certificates + .iter() + .find(|c| c.id == *id) + .unwrap(); + assert_eq!(certificate.name, *name); + } + + // by user db id + let request = GetUserCertificatesRequest { + user_principal: None, + user_db_id: Some(TEST_USER_DB_ID.to_string()), + }; + let res_by_id = get_user_certificates(&env, calling_principal, request).unwrap(); + assert_eq!(res_by_id.certificates.len(), TEST_CERTIFICATES_COUNT); + // check that all certificates are present + for (id, name) in created_certificates_previews.iter() { + let certificate = res_by_id.certificates.iter().find(|c| c.id == *id).unwrap(); + assert_eq!(certificate.name, *name); + } + }; + + test(user_principal); + test(backend_principal); + // check that certification is still valid after canister upgrade + upgrade_canister(&env); + test(user_principal); + test(backend_principal); +} + +#[test] +fn test_get_user_certificates_another_user() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + let first_user_db_id = TEST_USER_DB_ID; + let first_user_identity = create_user( + &env, + &auth_provider_key_pair, + TEST_USER_SUB, + first_user_db_id, + ); + let first_user_principal = first_user_identity.sender().unwrap(); + + let second_user_sub = "test_sub_2"; + let second_user_db_id = "ccb31f93-1a16-4089-bc84-1822ae591da2"; + let second_user_identity = create_user( + &env, + &auth_provider_key_pair, + second_user_sub, + second_user_db_id, + ); + + const TEST_CERTIFICATES_COUNT: usize = 10; + + for _ in 0..TEST_CERTIFICATES_COUNT { + create_test_certificate(&env, backend_principal, first_user_db_id.to_string()); + } + + let second_user_principal = second_user_identity.sender().unwrap(); + // by user principal + let request = GetUserCertificatesRequest { + user_principal: Some(first_user_principal), + user_db_id: None, + }; + let res_principal = get_user_certificates(&env, second_user_principal, request).unwrap_err(); + assert!( + extract_trap_message(res_principal).contains("User can only access their own certificates") + ); + + // by user db id + let request = GetUserCertificatesRequest { + user_principal: None, + user_db_id: Some(first_user_db_id.to_string()), + }; + let res_db_id = get_user_certificates(&env, second_user_principal, request).unwrap_err(); + assert!(extract_trap_message(res_db_id).contains("User can only access their own certificates")); + + // managed user + const MANAGED_USER_DB_ID: &str = "ccb31f93-1a16-4089-bc84-1822ae591da2"; + create_test_certificate_for_managed_user( + &env, + backend_principal, + first_user_db_id.to_string(), + MANAGED_USER_DB_ID.to_string(), + ); + let request = GetUserCertificatesRequest { + user_principal: None, + user_db_id: Some(MANAGED_USER_DB_ID.to_string()), + }; + let res_managed = get_user_certificates(&env, second_user_principal, request).unwrap(); + assert_eq!(res_managed.certificates.len(), 0); +} + +#[test] +fn test_get_user_certificates_no_user() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + + const TEST_CERTIFICATES_COUNT: usize = 10; + + for _ in 0..TEST_CERTIFICATES_COUNT { + create_test_certificate(&env, backend_principal, TEST_USER_DB_ID.to_string()); + } + + // by user principal + let non_existing_user_principal = generate_random_identity().sender().unwrap(); + let request = GetUserCertificatesRequest { + user_principal: Some(non_existing_user_principal), + user_db_id: None, + }; + let res_principal = get_user_certificates(&env, backend_principal, request).unwrap(); + assert_eq!(res_principal.certificates.len(), 0); + + // by user db id + let non_existing_user_db_id = "ccb31f93-1a16-4089-bc84-1822ae591da2"; + let request = GetUserCertificatesRequest { + user_principal: None, + user_db_id: Some(non_existing_user_db_id.to_string()), + }; + let res_db_id = get_user_certificates(&env, backend_principal, request).unwrap(); + assert_eq!(res_db_id.certificates.len(), 0); +} + +#[test] +fn test_get_user_certificates_managed_user() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + let user_identity = setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + let user_principal = user_identity.sender().unwrap(); + + const MANAGED_USER_DB_ID: &str = "ccb31f93-1a16-4089-bc84-1822ae591da2"; + + const TEST_CERTIFICATES_COUNT: usize = 10; + let mut created_certificates_previews = vec![]; + for _ in 0..TEST_CERTIFICATES_COUNT { + let res = create_test_certificate_for_managed_user( + &env, + backend_principal, + TEST_USER_DB_ID.to_string(), + MANAGED_USER_DB_ID.to_string(), + ); + created_certificates_previews.push(res); + } + + let test = |calling_principal: Principal| { + let request = GetUserCertificatesRequest { + user_principal: None, + user_db_id: Some(MANAGED_USER_DB_ID.to_string()), + }; + let res = get_user_certificates(&env, calling_principal, request).unwrap(); + assert_eq!(res.certificates.len(), TEST_CERTIFICATES_COUNT); + // check that all certificates are present + for (id, name) in created_certificates_previews.iter() { + let certificate = res.certificates.iter().find(|c| c.id == *id).unwrap(); + assert_eq!(certificate.name, *name); + } + }; + + test(user_principal); + test(backend_principal); +} + +#[test] +fn test_get_certificate() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + let user_identity = setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + let user_principal = user_identity.sender().unwrap(); + + const TEST_CERTIFICATES_COUNT: usize = 10; + let mut created_certificates_previews = vec![]; + for _ in 0..TEST_CERTIFICATES_COUNT { + let res = create_test_certificate(&env, backend_principal, TEST_USER_DB_ID.to_string()); + created_certificates_previews.push(res); + } + + let test = |calling_principal: Principal| { + for (id, name) in created_certificates_previews.iter() { + let res_by_id = get_certificate(&env, calling_principal, id.clone()).unwrap(); + let certificate = decode_certificate(&res_by_id.certificate.certificate_cbor_hex); + assert_eq!(certificate.user_principal, user_principal); + assert_eq!(certificate.content.name, *name); + assert_ic_certification_is_valid( + &env, + res_by_id.ic_certificate, + res_by_id.ic_certificate_witness.clone(), + ); + assert_ic_certificate_tree_is_valid( + res_by_id.ic_certificate_witness, + &user_principal, + vec![res_by_id.certificate], + ); + } + }; + + test(user_principal); + test(backend_principal); +} + +#[test] +fn test_get_certificate_managed_user() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + let user_identity = setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + let user_principal = user_identity.sender().unwrap(); + + const MANAGED_USER_DB_ID: &str = "ccb31f93-1a16-4089-bc84-1822ae591da2"; + let (certificate_id, name) = create_test_certificate_for_managed_user( + &env, + backend_principal, + TEST_USER_DB_ID.to_string(), + MANAGED_USER_DB_ID.to_string(), + ); + + let test = |calling_principal: Principal| { + let res = get_certificate(&env, calling_principal, certificate_id.clone()).unwrap(); + let certificate = decode_certificate(&res.certificate.certificate_cbor_hex); + assert_eq!(certificate.user_principal, user_principal); + assert_eq!(certificate.content.name, name); + assert_eq!( + certificate.managed_user_id.clone().unwrap(), + MANAGED_USER_DB_ID.to_string() + ); + assert_ic_certification_is_valid( + &env, + res.ic_certificate, + res.ic_certificate_witness.clone(), + ); + assert_ic_certificate_tree_is_valid( + res.ic_certificate_witness, + &user_principal, + vec![res.certificate], + ); + }; + + test(user_principal); + test(backend_principal); +} + +#[test] +fn test_get_certificate_not_found() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + + const TEST_CERTIFICATES_COUNT: usize = 10; + + for _ in 0..TEST_CERTIFICATES_COUNT { + create_test_certificate(&env, backend_principal, TEST_USER_DB_ID.to_string()); + } + + let non_existing_certificate_id = "ccb31f93-1a16-4089-bc84-1822ae591da2"; + let res = get_certificate( + &env, + backend_principal, + non_existing_certificate_id.to_string(), + ) + .unwrap_err(); + assert!(extract_trap_message(res).contains("Certificate not found")); +} + +#[test] +fn test_get_certificate_another_user() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + let first_user_db_id = TEST_USER_DB_ID; + create_user( + &env, + &auth_provider_key_pair, + TEST_USER_SUB, + first_user_db_id, + ); + + let second_user_sub = "test_sub_2"; + let second_user_db_id = "ccb31f93-1a16-4089-bc84-1822ae591da2"; + let second_user_identity = create_user( + &env, + &auth_provider_key_pair, + second_user_sub, + second_user_db_id, + ); + + let (certificate_id, _) = + create_test_certificate(&env, backend_principal, first_user_db_id.to_string()); + + let second_user_principal = second_user_identity.sender().unwrap(); + let res_principal = get_certificate(&env, second_user_principal, certificate_id).unwrap_err(); + assert!( + extract_trap_message(res_principal).contains("User can only access their own certificates") + ); + + // managed user + const MANAGED_USER_DB_ID: &str = "ccb31f93-1a16-4089-bc84-1822ae591da2"; + let (certificate_id, _) = create_test_certificate_for_managed_user( + &env, + backend_principal, + first_user_db_id.to_string(), + MANAGED_USER_DB_ID.to_string(), + ); + let res_managed = get_certificate(&env, second_user_principal, certificate_id).unwrap_err(); + assert!( + extract_trap_message(res_managed).contains("User can only access their own certificates") + ); +} + +#[test] +fn test_get_certificate_not_authorized() { + let env = test_env::create_test_env(); + let backend_principal = setup_config(&env); + setup_user(&env, TEST_USER_SUB, TEST_USER_DB_ID); + + let (certificate_id, _) = + create_test_certificate(&env, backend_principal, TEST_USER_DB_ID.to_string()); + + for sender in [ + generate_random_identity().sender().unwrap(), + Principal::anonymous(), + ] { + let res = get_certificate(&env, sender, certificate_id.clone()).unwrap_err(); + assert!( + extract_trap_message(res).contains("Caller is not the backend or a registered user") + ); + } +} + +fn assert_ic_certification_is_valid( + test_env: &TestEnv, + ic_certificate: Vec, + ic_certificate_witness: Vec, +) { + let cert: IcCertificate = serde_cbor::from_slice(&ic_certificate).unwrap(); + let canister_id = test_env.canister_id(); + let canister_id_bytes = canister_id.as_slice(); + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_nanos(); + + cert.verify( + canister_id_bytes, + test_env.root_ic_key(), + ¤t_time, + &MAX_IC_CERT_TIME_OFFSET_NS, + ) + .unwrap(); + + let tree: HashTree = serde_cbor::from_slice(&ic_certificate_witness).unwrap(); + match cert + .tree + .lookup_path(vec![b"canister", canister_id_bytes, b"certified_data"]) + { + LookupResult::Found(witness) => assert_eq!(witness, tree.digest()), + _ => panic!("expected LookupResult::Found"), + } +} + +fn assert_ic_certificate_tree_is_valid( + ic_certificate_witness: Vec, + user_principal: &Principal, + certificates: Vec, +) { + // SSP certificates tree structure: + // ssp_certificates + // └── + // └── + // └── certificate cbor data hash + let tree: HashTree = serde_cbor::from_slice(&ic_certificate_witness).unwrap(); + for cert in certificates.iter() { + let id = Uuid::parse_str(cert.id.as_str()).unwrap(); + match tree.lookup_subtree(vec![ + b"ssp_certificates", + user_principal.as_ref(), + id.as_bytes(), + ]) { + SubtreeLookupResult::Found(witness_cert) => { + let certificate_cbor = hex::decode(cert.certificate_cbor_hex.clone()).unwrap(); + match witness_cert.as_ref() { + HashTreeNode::Leaf(value) => { + assert_eq!(*value, leaf_hash(&certificate_cbor)); + } + _ => panic!("expected HashTreeNode::Leaf"), + }; + } + _ => panic!("expected LookupResult::Found"), + } + } +} diff --git a/apps/ssp_backend/tests/common/auth_provider.rs b/apps/ssp_backend/tests/common/auth_provider.rs new file mode 100644 index 0000000..159bb52 --- /dev/null +++ b/apps/ssp_backend/tests/common/auth_provider.rs @@ -0,0 +1,74 @@ +use base64::{engine::general_purpose, Engine as _}; +use jwt_simple::prelude::*; +use ssp_backend_types::{Auth0JWK, Auth0JWKSet, HasuraJWTClaims}; + +// ignore rust-analyzer errors on these environment variables +// compilation succeeds if you've correctly set the .env file +const AUTH0_ISSUER: &str = env!("ID_TOKEN_ISSUER_BASE_URL"); // expected to have a trailing slash +const AUTH0_AUDIENCE: &str = env!("ID_TOKEN_AUDIENCE"); + +const KEY_ID: &str = "integration_tests_key_id"; + +fn component_to_base64(component: &[u8]) -> String { + general_purpose::URL_SAFE_NO_PAD.encode(component) +} + +pub fn initialize_auth_provider() -> (RS256KeyPair, Auth0JWKSet) { + let key_pair = create_key_pair(); + let jwks = create_jwks(&key_pair); + + (key_pair, jwks) +} + +pub fn create_key_pair() -> RS256KeyPair { + RS256KeyPair::generate(2048).unwrap().with_key_id(KEY_ID) +} + +pub fn create_jwks(key_pair: &RS256KeyPair) -> Auth0JWKSet { + let pk = key_pair.public_key(); + let components = pk.to_components(); + Auth0JWKSet { + keys: vec![Auth0JWK { + kid: key_pair.key_id().as_ref().unwrap().to_string(), + kty: "RSA".to_string(), + alg: RS256KeyPair::jwt_alg_name().to_string(), + r#use: "sig".to_string(), + n: component_to_base64(&components.n), + e: component_to_base64(&components.e), + // not needed + x5c: vec!["".to_string()], + x5t: "".to_string(), + }], + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CustomClaims { + #[serde(rename = "https://hasura.io/jwt/claims")] + pub hasura_claims: Option, +} + +pub fn create_jwt( + key_pair: &RS256KeyPair, + sub: &str, + nonce: &str, + db_id: Option<&str>, + valid_for: Duration, +) -> (String, JWTClaims) { + let custom_claims = CustomClaims { + hasura_claims: db_id.map(|db_id| HasuraJWTClaims { + x_hasura_default_role: "user".to_string(), + x_hasura_allowed_roles: vec!["user".to_string()], + x_hasura_user_id: db_id.to_string(), + }), + }; + + let claims = Claims::with_custom_claims(custom_claims, valid_for) + .with_issuer(AUTH0_ISSUER) + .with_audience(AUTH0_AUDIENCE) + .with_subject(sub) + .with_nonce(nonce); + let jwt = key_pair.sign(claims.clone()).unwrap(); + + (jwt, claims) +} diff --git a/apps/ssp_backend/tests/common/canister.rs b/apps/ssp_backend/tests/common/canister.rs new file mode 100644 index 0000000..a8d0f52 --- /dev/null +++ b/apps/ssp_backend/tests/common/canister.rs @@ -0,0 +1,134 @@ +use candid::Principal; +use pocket_ic::{query_candid_as, update_candid_as, CallError, ErrorCode, UserError}; +use ssp_backend_types::{ + Auth0JWKSet, Config, CreateCertificateRequest, CreateCertificateResponse, + GetCertificateResponse, GetDelegationResponse, GetUserCertificatesRequest, + GetUserCertificatesResponse, PrepareDelegationResponse, User, +}; + +use super::test_env::TestEnv; + +pub fn initialize_canister(env: &TestEnv, jwks: Auth0JWKSet) { + set_jwks(env, env.controller(), jwks).unwrap(); +} + +pub fn extract_trap_message(res: CallError) -> String { + match res { + CallError::UserError(UserError { + code: ErrorCode::CanisterCalledTrap, + description, + }) => description, + _ => panic!("expected trap"), + } +} + +pub fn prepare_delegation( + env: &TestEnv, + sender: Principal, + jwt: String, +) -> Result { + update_candid_as( + env.pic(), + env.canister_id(), + sender, + "prepare_delegation", + (jwt,), + ) + .map(|(res,)| res) +} + +pub fn get_delegation( + env: &TestEnv, + sender: Principal, + jwt: String, + expiration: u64, +) -> Result { + query_candid_as( + env.pic(), + env.canister_id(), + sender, + "get_delegation", + (jwt, expiration), + ) + .map(|(res,)| res) +} + +pub fn sync_jwks(env: &TestEnv, sender: Principal) -> Result<(), CallError> { + update_candid_as(env.pic(), env.canister_id(), sender, "sync_jwks", ()).map(|(res,)| res) +} + +pub fn set_jwks(env: &TestEnv, sender: Principal, jwks: Auth0JWKSet) -> Result<(), CallError> { + update_candid_as(env.pic(), env.canister_id(), sender, "set_jwks", (jwks,)).map(|(res,)| res) +} + +pub fn get_jwks(env: &TestEnv, sender: Principal) -> Result, CallError> { + query_candid_as(env.pic(), env.canister_id(), sender, "get_jwks", ()).map(|(res,)| res) +} + +pub fn get_config(env: &TestEnv, sender: Principal) -> Result { + query_candid_as(env.pic(), env.canister_id(), sender, "get_config", ()).map(|(res,)| res) +} + +pub fn set_backend_principal( + env: &TestEnv, + sender: Principal, + principal: Principal, +) -> Result<(), CallError> { + update_candid_as( + env.pic(), + env.canister_id(), + sender, + "set_backend_principal", + (principal,), + ) + .map(|(res,)| res) +} + +pub fn get_my_user(env: &TestEnv, sender: Principal) -> Result { + query_candid_as(env.pic(), env.canister_id(), sender, "get_my_user", ()).map(|(res,)| res) +} + +pub fn create_certificate( + env: &TestEnv, + sender: Principal, + request: CreateCertificateRequest, +) -> Result { + update_candid_as( + env.pic(), + env.canister_id(), + sender, + "create_certificate", + (request,), + ) + .map(|(res,)| res) +} + +pub fn get_user_certificates( + env: &TestEnv, + sender: Principal, + request: GetUserCertificatesRequest, +) -> Result { + query_candid_as( + env.pic(), + env.canister_id(), + sender, + "get_user_certificates", + (request,), + ) + .map(|(res,)| res) +} + +pub fn get_certificate( + env: &TestEnv, + sender: Principal, + id: String, +) -> Result { + query_candid_as( + env.pic(), + env.canister_id(), + sender, + "get_certificate", + (id,), + ) + .map(|(res,)| res) +} diff --git a/apps/ssp_backend/tests/common/date_time.rs b/apps/ssp_backend/tests/common/date_time.rs new file mode 100644 index 0000000..5086a11 --- /dev/null +++ b/apps/ssp_backend/tests/common/date_time.rs @@ -0,0 +1,10 @@ +use std::time::SystemTime; + +pub fn date_time_from_canister_time(canister_time: SystemTime) -> chrono::DateTime { + canister_time.into() +} + +pub fn date_time_str_from_canister_time(canister_time: SystemTime) -> String { + // same as on the canister + date_time_from_canister_time(canister_time).to_rfc3339_opts(chrono::SecondsFormat::Secs, false) +} diff --git a/apps/ssp_backend/tests/common/identity.rs b/apps/ssp_backend/tests/common/identity.rs new file mode 100644 index 0000000..796f6aa --- /dev/null +++ b/apps/ssp_backend/tests/common/identity.rs @@ -0,0 +1,36 @@ +use ic_agent::identity::{ + BasicIdentity, DelegatedIdentity, Delegation as IcDelegation, + SignedDelegation as IcSignedDelegation, +}; +use ring::{rand::SystemRandom, signature::Ed25519KeyPair}; +use serde_bytes::ByteBuf; +use ssp_backend_types::SignedDelegation; + +pub fn generate_random_identity() -> BasicIdentity { + let pkcs8 = Ed25519KeyPair::generate_pkcs8(&SystemRandom::new()).unwrap(); + let key_pair = Ed25519KeyPair::from_pkcs8(pkcs8.as_ref()).unwrap(); + BasicIdentity::from_key_pair(key_pair) +} + +pub fn pk_to_hex(pk: &[u8]) -> String { + hex::encode(pk) +} + +pub fn delegated_identity_from_delegation( + user_key: ByteBuf, + session_identity: BasicIdentity, + signed_delegation: SignedDelegation, +) -> DelegatedIdentity { + DelegatedIdentity::new( + user_key.to_vec(), + Box::new(session_identity), + vec![IcSignedDelegation { + delegation: IcDelegation { + pubkey: signed_delegation.delegation.pubkey.to_vec(), + expiration: signed_delegation.delegation.expiration, + targets: signed_delegation.delegation.targets, + }, + signature: signed_delegation.signature.to_vec(), + }], + ) +} diff --git a/apps/ssp_backend/tests/common/mod.rs b/apps/ssp_backend/tests/common/mod.rs new file mode 100644 index 0000000..55a115f --- /dev/null +++ b/apps/ssp_backend/tests/common/mod.rs @@ -0,0 +1,5 @@ +pub mod auth_provider; +pub mod canister; +pub mod date_time; +pub mod identity; +pub mod test_env; diff --git a/apps/ssp_backend/tests/common/test_env.rs b/apps/ssp_backend/tests/common/test_env.rs new file mode 100644 index 0000000..b7c57ea --- /dev/null +++ b/apps/ssp_backend/tests/common/test_env.rs @@ -0,0 +1,119 @@ +use std::io::Read; +use std::path::PathBuf; +use std::time::Duration; +use std::{fs::File, time::SystemTime}; + +use candid::Principal; +use ic_agent::Identity; +use pocket_ic::{PocketIc, PocketIcBuilder}; + +use super::identity::generate_random_identity; + +pub struct TestEnv { + pic: PocketIc, + canister_id: Principal, + root_ic_key: Vec, + controller: Principal, +} + +impl TestEnv { + /// Creates a new test env from the wasm module, + /// setting the PIC time to the current time. + pub fn new(wasm_module: Vec) -> Self { + let pic = PocketIcBuilder::new() + // NNS subnet needed to retrieve the root key + .with_nns_subnet() + .with_application_subnet() + .build(); + + // set ic time to current time + pic.set_time(SystemTime::now()); + + let controller = generate_random_identity().sender().unwrap(); + + let app_subnet = pic.topology().get_app_subnets()[0]; + let canister_id = pic.create_canister_on_subnet(Some(controller), None, app_subnet); + pic.add_cycles(canister_id, 1_000_000_000_000_000); // we don't care about the cycles + + pic.install_canister( + canister_id, + wasm_module, + candid::encode_args(()).unwrap(), + Some(controller), + ); + + let root_ic_key = pic.root_key().unwrap(); + + Self { + pic, + canister_id, + root_ic_key, + controller, + } + } + + pub fn pic(&self) -> &PocketIc { + &self.pic + } + + pub fn canister_id(&self) -> Principal { + self.canister_id + } + + pub fn controller(&self) -> Principal { + self.controller + } + + /// Sets the canister time by specifying the duration elapsed from [SystemTime::UNIX_EPOCH]. + pub fn set_canister_time(&self, elapsed: Duration) { + self.pic.set_time(SystemTime::UNIX_EPOCH + elapsed); + // produce and advance by some blocks to set the time correctly + self.ticks(10); + } + + /// Produce and advance by some blocks + pub fn ticks(&self, n: u64) { + for _ in 0..n { + self.pic.tick(); + } + } + + pub fn get_canister_time(&self) -> SystemTime { + self.pic.get_time() + } + + pub fn root_ic_key(&self) -> &[u8] { + &self.root_ic_key + } +} + +pub fn create_test_env() -> TestEnv { + let wasm_path = std::env::var("TEST_CANISTER_WASM_PATH").unwrap(); + let wasm_module = load_canister_wasm_from_path(&PathBuf::from(wasm_path)); + + TestEnv::new(wasm_module) +} + +/// Simulates a canister upgrade, using the same wasm module. +pub fn upgrade_canister(env: &TestEnv) { + let wasm_path = std::env::var("TEST_CANISTER_WASM_PATH").unwrap(); + let wasm_module = load_canister_wasm_from_path(&PathBuf::from(wasm_path)); + + env.pic() + .upgrade_canister( + env.canister_id(), + wasm_module, + candid::encode_args(()).unwrap(), + Some(env.controller()), + ) + .unwrap(); + env.ticks(10); +} + +fn load_canister_wasm_from_path(path: &PathBuf) -> Vec { + let mut file = File::open(path) + .unwrap_or_else(|_| panic!("Failed to open file: {}", path.to_str().unwrap())); + let mut bytes = Vec::new(); + file.read_to_end(&mut bytes).expect("Failed to read file"); + bytes +} diff --git a/apps/ssp_backend/tests/config.rs b/apps/ssp_backend/tests/config.rs new file mode 100644 index 0000000..8f6b591 --- /dev/null +++ b/apps/ssp_backend/tests/config.rs @@ -0,0 +1,76 @@ +pub mod common; + +use candid::Principal; +use common::{ + canister::{extract_trap_message, get_config, set_backend_principal}, + identity::generate_random_identity, + test_env, +}; +use ic_agent::Identity; +use ssp_backend_types::Config; + +#[test] +fn test_get_config_not_controller() { + let env = test_env::create_test_env(); + + let sender = generate_random_identity().sender().unwrap(); + + let res = get_config(&env, sender).unwrap_err(); + + assert!(extract_trap_message(res).contains("Caller is not a controller")); +} + +#[test] +fn test_get_config() { + let env = test_env::create_test_env(); + + let sender = env.controller(); + let res = get_config(&env, sender).unwrap(); + + assert_eq!( + res, + Config { + backend_principal: None + } + ); +} + +#[test] +fn test_set_backend_principal_not_controller() { + let env = test_env::create_test_env(); + + let sender = generate_random_identity().sender().unwrap(); + let principal = generate_random_identity().sender().unwrap(); + + let res = set_backend_principal(&env, sender, principal).unwrap_err(); + + assert!(extract_trap_message(res).contains("Caller is not a controller")); +} + +#[test] +fn test_set_backend_principal_anonymous() { + let env = test_env::create_test_env(); + + let sender = env.controller(); + let principal = Principal::anonymous(); + + let res = set_backend_principal(&env, sender, principal).unwrap_err(); + + assert!(extract_trap_message(res).contains("Backend principal cannot be anonymous")); +} + +#[test] +fn test_set_backend_principal() { + let env = test_env::create_test_env(); + + let sender = env.controller(); + let original_config = get_config(&env, sender).unwrap(); + assert!(original_config.backend_principal.is_none()); + + let principal = generate_random_identity().sender().unwrap(); + + set_backend_principal(&env, sender, principal).unwrap(); + let updated_config = get_config(&env, sender).unwrap(); + + assert_eq!(updated_config.backend_principal.unwrap(), principal); +} diff --git a/apps/ssp_backend/tests/controller_only.rs b/apps/ssp_backend/tests/controller_only.rs new file mode 100644 index 0000000..6119639 --- /dev/null +++ b/apps/ssp_backend/tests/controller_only.rs @@ -0,0 +1,64 @@ +pub mod common; + +use common::{ + canister::{extract_trap_message, get_jwks, set_jwks, sync_jwks}, + identity::generate_random_identity, + test_env, +}; +use ic_agent::Identity; +use ssp_backend_types::Auth0JWKSet; + +#[test] +fn test_sync_jwks_controller_only() { + let env = test_env::create_test_env(); + + let sender = generate_random_identity().sender().unwrap(); + + let res = sync_jwks(&env, sender).unwrap_err(); + + assert!(extract_trap_message(res).contains("Caller is not a controller")); +} + +#[test] +fn test_set_jwks_controller_only() { + let env = test_env::create_test_env(); + + let sender = generate_random_identity().sender().unwrap(); + + let res = set_jwks(&env, sender, Auth0JWKSet { keys: vec![] }).unwrap_err(); + + assert!(extract_trap_message(res).contains("Caller is not a controller")); +} + +#[test] +fn test_set_jwks_once() { + let env = test_env::create_test_env(); + + // initially, the canister doesn't have the jwks + let canister_jwks = get_jwks(&env, env.controller()).unwrap(); + assert!(canister_jwks.is_none()); + + // set dummy jwks + let jwks = Auth0JWKSet { keys: vec![] }; + set_jwks(&env, env.controller(), jwks.clone()).unwrap(); + + // now the canister has the jwks + let canister_jwks = get_jwks(&env, env.controller()).unwrap().unwrap(); + assert_eq!(canister_jwks, jwks); + + // try to set the jwks again + let res = set_jwks(&env, env.controller(), jwks).unwrap_err(); + assert!(extract_trap_message(res) + .contains("JWKS already set. Call sync_jwks to fetch the JWKS from the auth provider")); +} + +#[test] +fn test_get_jwks_controller_only() { + let env = test_env::create_test_env(); + + let sender = generate_random_identity().sender().unwrap(); + + let res = get_jwks(&env, sender).unwrap_err(); + + assert!(extract_trap_message(res).contains("Caller is not a controller")); +} diff --git a/apps/ssp_backend/tests/delegation.rs b/apps/ssp_backend/tests/delegation.rs new file mode 100644 index 0000000..03eb537 --- /dev/null +++ b/apps/ssp_backend/tests/delegation.rs @@ -0,0 +1,490 @@ +pub mod common; + +use std::time::SystemTime; + +use candid::Principal; +use ic_agent::Identity; +use ic_representation_independent_hash::{representation_independent_hash, Value}; +use jwt_simple::prelude::*; +use ssp_backend_types::{ + GetDelegationResponse, PrepareDelegationResponse, SignedDelegation, UserKey, +}; + +use common::{ + auth_provider::{create_jwt, initialize_auth_provider}, + canister::{extract_trap_message, get_delegation, initialize_canister, prepare_delegation}, + identity::{generate_random_identity, pk_to_hex}, + test_env::{create_test_env, upgrade_canister, TestEnv}, +}; + +const NANOS_IN_SECONDS: u64 = 1_000_000_000; + +/// Same as on the canister +const MAX_IAT_AGE_SECONDS: u64 = 10 * 60; // 10 minutes +/// Same as on Auth0 +const JWT_VALID_FOR_HOURS: u64 = 10; + +const TEST_USER_SUB: &str = "test_sub"; +const TEST_USER_DB_ID: &str = "fafc11f5-c784-4cbe-9fbf-207889afd519"; + +fn verify_delegation( + env: &TestEnv, + user_key: UserKey, + signed_delegation: &SignedDelegation, + root_key: &[u8], +) { + const DOMAIN_SEPARATOR: &[u8] = b"ic-request-auth-delegation"; + + // The signed message is a signature domain separator + // followed by the representation independent hash of a map with entries + // pubkey, expiration and targets (if any), using the respective values from the delegation. + // See https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication for details + let key_value_pairs = vec![ + ( + "pubkey".to_string(), + Value::Bytes(signed_delegation.delegation.pubkey.clone().into_vec()), + ), + ( + "expiration".to_string(), + Value::Number(signed_delegation.delegation.expiration), + ), + ]; + let mut msg: Vec = Vec::from([(DOMAIN_SEPARATOR.len() as u8)]); + msg.extend_from_slice(DOMAIN_SEPARATOR); + msg.extend_from_slice(&representation_independent_hash(&key_value_pairs)); + + env.pic() + .verify_canister_signature( + msg, + signed_delegation.signature.clone().into_vec(), + user_key.into_vec(), + root_key.to_vec(), + ) + .expect("delegation signature invalid"); +} + +#[test] +fn test_prepare_delegation() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, claims) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let res = prepare_delegation(&env, session_principal, jwt).unwrap(); + + assert_eq!( + res.expiration, + claims.expires_at.unwrap().as_secs() * NANOS_IN_SECONDS + ) +} + +#[test] +fn test_prepare_delegation_wrong_identity() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let wrong_identity = generate_random_identity(); + let res = prepare_delegation(&env, wrong_identity.sender().unwrap(), jwt).unwrap_err(); + + assert!(extract_trap_message(res).contains("caller and token principal mismatch")); +} + +#[test] +fn test_prepare_delegation_anonymous() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let res = prepare_delegation(&env, Principal::anonymous(), jwt).unwrap_err(); + + assert!(extract_trap_message(res).contains("caller and token principal mismatch")); +} + +#[test] +fn test_prepare_delegation_wrong_claims() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let session_public_key = session_identity.public_key().unwrap(); + let (_, claims) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + // wrong issuer + { + let mut claims = claims.clone(); + claims.issuer = Some("wrong".to_string()); + let jwt = auth_provider_key_pair.sign(claims).unwrap(); + let res = prepare_delegation(&env, session_principal, jwt).unwrap_err(); + + assert!(extract_trap_message(res).contains("IssuerMismatch")); + } + + // wrong audience + { + let mut claims = claims.clone(); + claims.audiences = Some(Audiences::AsString("wrong".to_string())); + let jwt = auth_provider_key_pair.sign(claims).unwrap(); + let res = prepare_delegation(&env, session_principal, jwt).unwrap_err(); + + assert!(extract_trap_message(res).contains("AudienceMismatch")); + } + + // iat too old + { + let mut claims = claims.clone(); + let issued_at = claims.issued_at.unwrap(); + + let time = SystemTime::UNIX_EPOCH + .checked_add(issued_at.into()) + .unwrap(); + env.pic().set_time(time); + + claims.issued_at = Some(issued_at - Duration::from_secs(MAX_IAT_AGE_SECONDS + 1)); + let jwt = auth_provider_key_pair.sign(claims).unwrap(); + let res = prepare_delegation(&env, session_principal, jwt).unwrap_err(); + + assert!(extract_trap_message(res).contains("IatTooOld")); + } + + // expired + { + let mut claims = claims.clone(); + let expires_at = claims.expires_at.unwrap(); + + let time = SystemTime::UNIX_EPOCH + .checked_add(expires_at.into()) + .unwrap(); + env.pic().set_time(time); + + claims.expires_at = Some(expires_at - Duration::from_secs(1)); + let jwt = auth_provider_key_pair.sign(claims).unwrap(); + let res = prepare_delegation(&env, session_principal, jwt).unwrap_err(); + + assert!(extract_trap_message(res).contains("TokenExpired")); + } +} + +#[test] +fn test_prepare_delegation_user_id_not_found() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + None, + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let res = prepare_delegation(&env, session_principal, jwt).unwrap_err(); + + assert!(extract_trap_message(res).contains("User ID not found in hasura claims")); +} + +#[test] +fn test_prepare_delegation_across_upgrades() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks.clone()); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let res_before_upgrade = prepare_delegation(&env, session_principal, jwt.clone()).unwrap(); + + upgrade_canister(&env); + initialize_canister(&env, jwks); + + let res_after_upgrade = prepare_delegation(&env, session_principal, jwt).unwrap(); + + assert_eq!(res_before_upgrade.user_key, res_after_upgrade.user_key); +} + +#[test] +fn test_prepare_delegation_different_sessions() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks.clone()); + + let session1_identity = generate_random_identity(); + let session1_principal = session1_identity.sender().unwrap(); + let session1_public_key = session1_identity.public_key().unwrap(); + let (jwt1, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session1_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let res_session1 = prepare_delegation(&env, session1_principal, jwt1).unwrap(); + + let session2_identity = generate_random_identity(); + let session2_principal = session2_identity.sender().unwrap(); + let session2_public_key = session2_identity.public_key().unwrap(); + let (jwt2, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session2_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let res_session2 = prepare_delegation(&env, session2_principal, jwt2).unwrap(); + + assert_eq!(res_session1.user_key, res_session2.user_key); +} + +#[test] +fn test_get_delegation() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let PrepareDelegationResponse { + expiration, + user_key, + } = prepare_delegation(&env, session_principal, jwt.clone()).unwrap(); + + let res = get_delegation(&env, session_principal, jwt, expiration).unwrap(); + + match res { + GetDelegationResponse::SignedDelegation(signed_delegation) => { + assert_eq!(signed_delegation.delegation.pubkey, session_public_key); + assert_eq!(signed_delegation.delegation.expiration, expiration); + assert!(signed_delegation.delegation.targets.is_none()); + + verify_delegation(&env, user_key, &signed_delegation, env.root_ic_key()); + } + _ => panic!("Expected SignedDelegation"), + } +} + +#[test] +fn test_get_delegation_wrong_sub() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let PrepareDelegationResponse { expiration, .. } = + prepare_delegation(&env, session_principal, jwt.clone()).unwrap(); + + let (wrong_jwt, _) = create_jwt( + &auth_provider_key_pair, + "wrong_sub", + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let res = get_delegation(&env, session_principal, wrong_jwt, expiration).unwrap(); + + assert_eq!(res, GetDelegationResponse::NoSuchDelegation); +} + +#[test] +fn test_get_delegation_wrong_expiration() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + prepare_delegation(&env, session_principal, jwt.clone()).unwrap(); + + let res = get_delegation(&env, session_principal, jwt, 0).unwrap(); + + assert_eq!(res, GetDelegationResponse::NoSuchDelegation); +} + +#[test] +fn test_get_delegation_wrong_identity() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let wrong_identity = generate_random_identity(); + let res = get_delegation(&env, wrong_identity.sender().unwrap(), jwt, 0).unwrap_err(); + + assert!(extract_trap_message(res).contains("caller and token principal mismatch")); +} + +#[test] +fn test_get_delegation_anonymous() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_public_key = session_identity.public_key().unwrap(); + let (jwt, _) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + let res = get_delegation(&env, Principal::anonymous(), jwt, 0).unwrap_err(); + + assert!(extract_trap_message(res).contains("caller and token principal mismatch")); +} + +#[test] +fn test_get_delegation_wrong_claims() { + let env = create_test_env(); + let (auth_provider_key_pair, jwks) = initialize_auth_provider(); + initialize_canister(&env, jwks); + + let session_identity = generate_random_identity(); + let session_principal = session_identity.sender().unwrap(); + let session_public_key = session_identity.public_key().unwrap(); + let (_, claims) = create_jwt( + &auth_provider_key_pair, + TEST_USER_SUB, + &pk_to_hex(&session_public_key), + Some(TEST_USER_DB_ID), + Duration::from_hours(JWT_VALID_FOR_HOURS), + ); + + // wrong issuer + { + let mut claims = claims.clone(); + claims.issuer = Some("wrong".to_string()); + let jwt = auth_provider_key_pair.sign(claims).unwrap(); + let res = get_delegation(&env, session_principal, jwt, 0).unwrap_err(); + + assert!(extract_trap_message(res).contains("IssuerMismatch")); + } + + // wrong audience + { + let mut claims = claims.clone(); + claims.audiences = Some(Audiences::AsString("wrong".to_string())); + let jwt = auth_provider_key_pair.sign(claims).unwrap(); + let res = get_delegation(&env, session_principal, jwt, 0).unwrap_err(); + + assert!(extract_trap_message(res).contains("AudienceMismatch")); + } + + // iat too old + { + let mut claims = claims.clone(); + let issued_at = claims.issued_at.unwrap(); + + env.set_canister_time(issued_at.into()); + + claims.issued_at = Some(issued_at - Duration::from_secs(MAX_IAT_AGE_SECONDS + 1)); + let jwt = auth_provider_key_pair.sign(claims).unwrap(); + let res = get_delegation(&env, session_principal, jwt, 0).unwrap_err(); + + assert!(extract_trap_message(res).contains("IatTooOld")); + } + + // expired + { + let mut claims = claims.clone(); + let expires_at = claims.expires_at.unwrap(); + + env.set_canister_time(expires_at.into()); + + claims.expires_at = Some(expires_at - Duration::from_secs(1)); + let jwt = auth_provider_key_pair.sign(claims).unwrap(); + let res = get_delegation(&env, session_principal, jwt, 0).unwrap_err(); + + assert!(extract_trap_message(res).contains("TokenExpired")); + } +} diff --git a/apps/ssp_backend/turbo.json b/apps/ssp_backend/turbo.json new file mode 100644 index 0000000..243d645 --- /dev/null +++ b/apps/ssp_backend/turbo.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "tasks": { + "deploy": { + "env": [ + "ID_TOKEN_ISSUER_BASE_URL", + "ID_TOKEN_AUDIENCE", + "DFX_NETWORK", + "ENV_FILE_PATH" + ] + } + } +} diff --git a/canister_ids.json b/canister_ids.json new file mode 100644 index 0000000..a54033f --- /dev/null +++ b/canister_ids.json @@ -0,0 +1,5 @@ +{ + "ssp_backend": { + "ic": "f774v-kaaaa-aaaao-a3sfa-cai" + } +} diff --git a/dfx.json b/dfx.json new file mode 100644 index 0000000..f33828e --- /dev/null +++ b/dfx.json @@ -0,0 +1,28 @@ +{ + "canisters": { + "ssp_backend": { + "type": "custom", + "wasm": "./target/wasm32-unknown-unknown/release/ssp_backend.wasm", + "candid": "apps/ssp_backend/ssp_backend.did", + "package": "ssp_backend", + "build": ["apps/ssp_backend/scripts/build-canister.sh"], + "declarations": { + "bindings": ["js", "ts"], + "output": "packages/ssp-backend/src/generated" + }, + "metadata": [ + { + "name": "candid:service" + } + ] + } + }, + "defaults": { + "build": { + "args": "", + "packtool": "" + } + }, + "output_env_file": "packages/config/.env", + "version": 1 +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..711eb16 --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "xgs-monorepo", + "private": true, + "scripts": { + "turbo": "turbo --cache-dir=.turbo", + "build": "pnpm turbo build", + "dev": "TURBO_EXPERIMENTAL_UI=true pnpm turbo dev", + "lint": "pnpm turbo lint", + "format": "prettier --write . && cargo fmt --verbose", + "format:check": "prettier --check . && cargo fmt -- --check", + "deploy:local": "pnpm turbo deploy:local", + "deploy": "pnpm turbo deploy", + "test": "pnpm turbo test" + }, + "dependencies": { + "@dfinity/agent": "^2.1.1", + "@dfinity/identity": "^2.1.1", + "@dfinity/principal": "^2.1.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@repo/eslint-config": "workspace:*", + "@repo/typescript-config": "workspace:*", + "@types/eslint": "^8.56.5", + "@types/node": "^20.11.24", + "@types/react": "^18.2.77", + "@types/react-dom": "^18.2.25", + "eslint": "^8.57.0", + "prettier": "^3.2.5", + "turbo": "^2.0.6", + "typescript": "^5.3.3", + "vercel": "^37.1.0" + }, + "engines": { + "node": ">=20" + }, + "packageManager": "pnpm@9.1.2", + "workspaces": [ + "apps/*", + "packages/*" + ] +} diff --git a/packages/config/.env.example b/packages/config/.env.example new file mode 100644 index 0000000..5e46b8a --- /dev/null +++ b/packages/config/.env.example @@ -0,0 +1,5 @@ +# dfx variables, automatically filled in by dfx when deploying the canister +# DFX CANISTER ENVIRONMENT VARIABLES +DFX_NETWORK='local' +CANISTER_ID_SSP_BACKEND='bd3sg-teaaa-aaaaa-qaaba-cai' +# END DFX CANISTER ENVIRONMENT VARIABLES \ No newline at end of file diff --git a/packages/config/README.md b/packages/config/README.md new file mode 100644 index 0000000..717db6e --- /dev/null +++ b/packages/config/README.md @@ -0,0 +1,3 @@ +# `config` + +This package is used to share configurations such as environment variables files. diff --git a/packages/config/package.json b/packages/config/package.json new file mode 100644 index 0000000..100e36a --- /dev/null +++ b/packages/config/package.json @@ -0,0 +1,5 @@ +{ + "name": "@repo/config", + "version": "0.0.0", + "private": true +} diff --git a/packages/eslint-config/README.md b/packages/eslint-config/README.md new file mode 100644 index 0000000..8b42d90 --- /dev/null +++ b/packages/eslint-config/README.md @@ -0,0 +1,3 @@ +# `@turbo/eslint-config` + +Collection of internal eslint configurations. diff --git a/packages/eslint-config/library.js b/packages/eslint-config/library.js new file mode 100644 index 0000000..c667cd1 --- /dev/null +++ b/packages/eslint-config/library.js @@ -0,0 +1,34 @@ +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: ["eslint:recommended", "prettier", "eslint-config-turbo"], + plugins: ["only-warn"], + globals: { + React: true, + JSX: true, + }, + env: { + node: true, + }, + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: [ + // Ignore dotfiles + ".*.js", + "node_modules/", + "dist/", + ], + overrides: [ + { + files: ["*.js?(x)", "*.ts?(x)"], + }, + ], +}; diff --git a/packages/eslint-config/next.js b/packages/eslint-config/next.js new file mode 100644 index 0000000..2a57b95 --- /dev/null +++ b/packages/eslint-config/next.js @@ -0,0 +1,45 @@ +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: [ + "eslint:recommended", + "prettier", + require.resolve("@vercel/style-guide/eslint/next"), + "eslint-config-turbo", + ], + globals: { + React: true, + JSX: true, + }, + env: { + node: true, + browser: true, + }, + plugins: ["only-warn", "@typescript-eslint"], + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: [ + // Ignore dotfiles + ".*.js", + "node_modules/", + ], + overrides: [{ files: ["*.js?(x)", "*.ts?(x)"] }], + rules: { + // see https://stackoverflow.com/a/63798664/5094892 + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + varsIgnorePattern: "^_", + }, + ], + }, +}; diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json new file mode 100644 index 0000000..179bd8c --- /dev/null +++ b/packages/eslint-config/package.json @@ -0,0 +1,19 @@ +{ + "name": "@repo/eslint-config", + "version": "0.0.0", + "private": true, + "files": [ + "library.js", + "next.js", + "react-internal.js" + ], + "devDependencies": { + "@vercel/style-guide": "^6.0.0", + "eslint-config-turbo": "^2.0.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-only-warn": "^1.1.0", + "@typescript-eslint/parser": "^7.7.1", + "@typescript-eslint/eslint-plugin": "^7.7.1", + "typescript": "^5.3.3" + } +} diff --git a/packages/eslint-config/react-internal.js b/packages/eslint-config/react-internal.js new file mode 100644 index 0000000..19e9949 --- /dev/null +++ b/packages/eslint-config/react-internal.js @@ -0,0 +1,48 @@ +const { resolve } = require("node:path"); + +const project = resolve(process.cwd(), "tsconfig.json"); + +/* + * This is a custom ESLint configuration for use with + * internal (bundled by their consumer) libraries + * that utilize React. + * + * This config extends the Vercel Engineering Style Guide. + * For more information, see https://github.com/vercel/style-guide + * + */ + +/** @type {import("eslint").Linter.Config} */ +module.exports = { + extends: ["eslint:recommended", "prettier", "eslint-config-turbo"], + plugins: ["only-warn", "@typescript-eslint"], + globals: { + React: true, + JSX: true, + }, + env: { + browser: true, + }, + settings: { + "import/resolver": { + typescript: { + project, + }, + }, + }, + ignorePatterns: [ + // Ignore dotfiles + ".*.js", + "node_modules/", + "dist/", + ], + overrides: [ + // Force ESLint to detect .tsx files + { files: ["*.js?(x)", "*.ts?(x)"] }, + ], + rules: { + // see https://stackoverflow.com/a/63798664/5094892 + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error"], + }, +}; diff --git a/packages/ssp-backend/README.md b/packages/ssp-backend/README.md new file mode 100644 index 0000000..6e6e9f8 --- /dev/null +++ b/packages/ssp-backend/README.md @@ -0,0 +1,5 @@ +# `ssp-backend` + +Exports the [`ssp_backend`](../../apps/ssp_backend/) canister's actor and its related types and helpers. + +Use the exported `createActor` function to initialize an actor with the provided canister ID, which you can get from the `.env` file automatically created in the [`config`](../config/) package. diff --git a/packages/ssp-backend/package.json b/packages/ssp-backend/package.json new file mode 100644 index 0000000..b12d068 --- /dev/null +++ b/packages/ssp-backend/package.json @@ -0,0 +1,12 @@ +{ + "name": "@repo/ssp-backend", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "dfx generate ssp_backend" + }, + "main": "src/index.ts", + "exports": { + ".": "./src/index.ts" + } +} diff --git a/packages/ssp-backend/src/generated/index.d.ts b/packages/ssp-backend/src/generated/index.d.ts new file mode 100644 index 0000000..219b973 --- /dev/null +++ b/packages/ssp-backend/src/generated/index.d.ts @@ -0,0 +1,50 @@ +import type { + ActorSubclass, + HttpAgentOptions, + ActorConfig, + Agent, +} from "@dfinity/agent"; +import type { Principal } from "@dfinity/principal"; +import type { IDL } from "@dfinity/candid"; + +import { _SERVICE } from './ssp_backend.did'; + +export declare const idlFactory: IDL.InterfaceFactory; +export declare const canisterId: string; + +export declare interface CreateActorOptions { + /** + * @see {@link Agent} + */ + agent?: Agent; + /** + * @see {@link HttpAgentOptions} + */ + agentOptions?: HttpAgentOptions; + /** + * @see {@link ActorConfig} + */ + actorOptions?: ActorConfig; +} + +/** + * Intializes an {@link ActorSubclass}, configured with the provided SERVICE interface of a canister. + * @constructs {@link ActorSubClass} + * @param {string | Principal} canisterId - ID of the canister the {@link Actor} will talk to + * @param {CreateActorOptions} options - see {@link CreateActorOptions} + * @param {CreateActorOptions["agent"]} options.agent - a pre-configured agent you'd like to use. Supercedes agentOptions + * @param {CreateActorOptions["agentOptions"]} options.agentOptions - options to set up a new agent + * @see {@link HttpAgentOptions} + * @param {CreateActorOptions["actorOptions"]} options.actorOptions - options for the Actor + * @see {@link ActorConfig} + */ +export declare const createActor: ( + canisterId: string | Principal, + options?: CreateActorOptions +) => ActorSubclass<_SERVICE>; + +/** + * Intialized Actor using default settings, ready to talk to a canister using its candid interface + * @constructs {@link ActorSubClass} + */ +export declare const ssp_backend: ActorSubclass<_SERVICE>; diff --git a/packages/ssp-backend/src/generated/index.js b/packages/ssp-backend/src/generated/index.js new file mode 100644 index 0000000..b1ca34f --- /dev/null +++ b/packages/ssp-backend/src/generated/index.js @@ -0,0 +1,42 @@ +import { Actor, HttpAgent } from "@dfinity/agent"; + +// Imports and re-exports candid interface +import { idlFactory } from "./ssp_backend.did.js"; +export { idlFactory } from "./ssp_backend.did.js"; + +/* CANISTER_ID is replaced by webpack based on node environment + * Note: canister environment variable will be standardized as + * process.env.CANISTER_ID_ + * beginning in dfx 0.15.0 + */ +export const canisterId = + process.env.CANISTER_ID_SSP_BACKEND; + +export const createActor = (canisterId, options = {}) => { + const agent = options.agent || new HttpAgent({ ...options.agentOptions }); + + if (options.agent && options.agentOptions) { + console.warn( + "Detected both agent and agentOptions passed to createActor. Ignoring agentOptions and proceeding with the provided agent." + ); + } + + // Fetch root key for certificate validation during development + if (process.env.DFX_NETWORK !== "ic") { + agent.fetchRootKey().catch((err) => { + console.warn( + "Unable to fetch root key. Check to ensure that your local replica is running" + ); + console.error(err); + }); + } + + // Creates an actor with using the candid interface and the HttpAgent + return Actor.createActor(idlFactory, { + agent, + canisterId, + ...options.actorOptions, + }); +}; + +export const ssp_backend = canisterId ? createActor(canisterId) : undefined; diff --git a/packages/ssp-backend/src/generated/ssp_backend.did.d.ts b/packages/ssp-backend/src/generated/ssp_backend.did.d.ts new file mode 100644 index 0000000..ebe35e8 --- /dev/null +++ b/packages/ssp-backend/src/generated/ssp_backend.did.d.ts @@ -0,0 +1,110 @@ +import type { Principal } from '@dfinity/principal'; +import type { ActorMethod } from '@dfinity/agent'; +import type { IDL } from '@dfinity/candid'; + +export interface Auth0JWK { + 'e' : string, + 'n' : string, + 'alg' : string, + 'kid' : string, + 'kty' : string, + 'use' : string, + 'x5c' : Array, + 'x5t' : string, +} +export interface Auth0JWKS { 'keys' : Array } +export interface Certificate { + 'user_principal' : Principal, + 'content' : CertificateContent, + 'created_at' : string, + 'managed_user_id' : [] | [string], +} +export interface CertificateContent { + 'issued_at' : string, + 'name' : string, + 'file_uri' : [] | [string], + 'issuer_club_name' : [] | [string], + 'issuer_full_name' : [] | [string], + 'notes' : [] | [string], + 'external_id' : [] | [string], + 'sport_category' : string, +} +export interface CertificatePreviewWithId { 'id' : string, 'name' : string } +export interface CertificateWithId { + 'id' : string, + 'certificate_cbor_hex' : string, +} +export interface Config { 'backend_principal' : [] | [Principal] } +export interface CreateCertificateContentRequest { + 'issued_at' : Timestamp, + 'name' : string, + 'file_uri' : [] | [string], + 'issuer_club_name' : [] | [string], + 'issuer_full_name' : [] | [string], + 'notes' : [] | [string], + 'external_id' : [] | [string], + 'sport_category' : string, +} +export interface CreateCertificateRequest { + 'content' : CreateCertificateContentRequest, + 'managed_user_db_id' : [] | [string], + 'user_db_id' : string, +} +export interface CreateCertificateResponse { 'id' : string } +export interface Delegation { + 'pubkey' : PublicKey, + 'targets' : [] | [Array], + 'expiration' : Timestamp, +} +export interface GetCertificateResponse { + 'certificate' : CertificateWithId, + 'ic_certificate' : Uint8Array | number[], + 'ic_certificate_witness' : Uint8Array | number[], +} +export type GetDelegationResponse = { 'no_such_delegation' : null } | + { 'signed_delegation' : SignedDelegation }; +export interface GetUserCertificatesRequest { + 'user_principal' : [] | [Principal], + 'user_db_id' : [] | [string], +} +export interface GetUserCertificatesResponse { + 'certificates' : Array, +} +export interface PrepareDelegationResponse { + 'user_key' : UserKey, + 'expiration' : Timestamp, +} +export type PublicKey = Uint8Array | number[]; +export type Signature = Uint8Array | number[]; +export interface SignedDelegation { + 'signature' : Signature, + 'delegation' : Delegation, +} +export type Timestamp = bigint; +export interface User { + 'sub' : string, + 'created_at' : string, + 'db_id' : string, +} +export type UserKey = PublicKey; +export interface _SERVICE { + 'create_certificate' : ActorMethod< + [CreateCertificateRequest], + CreateCertificateResponse + >, + 'get_certificate' : ActorMethod<[string], GetCertificateResponse>, + 'get_config' : ActorMethod<[], Config>, + 'get_delegation' : ActorMethod<[string, Timestamp], GetDelegationResponse>, + 'get_jwks' : ActorMethod<[], [] | [Auth0JWKS]>, + 'get_my_user' : ActorMethod<[], User>, + 'get_user_certificates' : ActorMethod< + [GetUserCertificatesRequest], + GetUserCertificatesResponse + >, + 'prepare_delegation' : ActorMethod<[string], PrepareDelegationResponse>, + 'set_backend_principal' : ActorMethod<[Principal], undefined>, + 'set_jwks' : ActorMethod<[Auth0JWKS], undefined>, + 'sync_jwks' : ActorMethod<[], undefined>, +} +export declare const idlFactory: IDL.InterfaceFactory; +export declare const init: (args: { IDL: typeof IDL }) => IDL.Type[]; diff --git a/packages/ssp-backend/src/generated/ssp_backend.did.js b/packages/ssp-backend/src/generated/ssp_backend.did.js new file mode 100644 index 0000000..9e05386 --- /dev/null +++ b/packages/ssp-backend/src/generated/ssp_backend.did.js @@ -0,0 +1,110 @@ +export const idlFactory = ({ IDL }) => { + const Timestamp = IDL.Nat64; + const CreateCertificateContentRequest = IDL.Record({ + 'issued_at' : Timestamp, + 'name' : IDL.Text, + 'file_uri' : IDL.Opt(IDL.Text), + 'issuer_club_name' : IDL.Opt(IDL.Text), + 'issuer_full_name' : IDL.Opt(IDL.Text), + 'notes' : IDL.Opt(IDL.Text), + 'external_id' : IDL.Opt(IDL.Text), + 'sport_category' : IDL.Text, + }); + const CreateCertificateRequest = IDL.Record({ + 'content' : CreateCertificateContentRequest, + 'managed_user_db_id' : IDL.Opt(IDL.Text), + 'user_db_id' : IDL.Text, + }); + const CreateCertificateResponse = IDL.Record({ 'id' : IDL.Text }); + const CertificateWithId = IDL.Record({ + 'id' : IDL.Text, + 'certificate_cbor_hex' : IDL.Text, + }); + const GetCertificateResponse = IDL.Record({ + 'certificate' : CertificateWithId, + 'ic_certificate' : IDL.Vec(IDL.Nat8), + 'ic_certificate_witness' : IDL.Vec(IDL.Nat8), + }); + const Config = IDL.Record({ 'backend_principal' : IDL.Opt(IDL.Principal) }); + const Signature = IDL.Vec(IDL.Nat8); + const PublicKey = IDL.Vec(IDL.Nat8); + const Delegation = IDL.Record({ + 'pubkey' : PublicKey, + 'targets' : IDL.Opt(IDL.Vec(IDL.Principal)), + 'expiration' : Timestamp, + }); + const SignedDelegation = IDL.Record({ + 'signature' : Signature, + 'delegation' : Delegation, + }); + const GetDelegationResponse = IDL.Variant({ + 'no_such_delegation' : IDL.Null, + 'signed_delegation' : SignedDelegation, + }); + const Auth0JWK = IDL.Record({ + 'e' : IDL.Text, + 'n' : IDL.Text, + 'alg' : IDL.Text, + 'kid' : IDL.Text, + 'kty' : IDL.Text, + 'use' : IDL.Text, + 'x5c' : IDL.Vec(IDL.Text), + 'x5t' : IDL.Text, + }); + const Auth0JWKS = IDL.Record({ 'keys' : IDL.Vec(Auth0JWK) }); + const User = IDL.Record({ + 'sub' : IDL.Text, + 'created_at' : IDL.Text, + 'db_id' : IDL.Text, + }); + const GetUserCertificatesRequest = IDL.Record({ + 'user_principal' : IDL.Opt(IDL.Principal), + 'user_db_id' : IDL.Opt(IDL.Text), + }); + const CertificatePreviewWithId = IDL.Record({ + 'id' : IDL.Text, + 'name' : IDL.Text, + }); + const GetUserCertificatesResponse = IDL.Record({ + 'certificates' : IDL.Vec(CertificatePreviewWithId), + }); + const UserKey = PublicKey; + const PrepareDelegationResponse = IDL.Record({ + 'user_key' : UserKey, + 'expiration' : Timestamp, + }); + return IDL.Service({ + 'create_certificate' : IDL.Func( + [CreateCertificateRequest], + [CreateCertificateResponse], + [], + ), + 'get_certificate' : IDL.Func( + [IDL.Text], + [GetCertificateResponse], + ['query'], + ), + 'get_config' : IDL.Func([], [Config], ['query']), + 'get_delegation' : IDL.Func( + [IDL.Text, Timestamp], + [GetDelegationResponse], + ['query'], + ), + 'get_jwks' : IDL.Func([], [IDL.Opt(Auth0JWKS)], ['query']), + 'get_my_user' : IDL.Func([], [User], ['query']), + 'get_user_certificates' : IDL.Func( + [GetUserCertificatesRequest], + [GetUserCertificatesResponse], + ['query'], + ), + 'prepare_delegation' : IDL.Func( + [IDL.Text], + [PrepareDelegationResponse], + [], + ), + 'set_backend_principal' : IDL.Func([IDL.Principal], [], []), + 'set_jwks' : IDL.Func([Auth0JWKS], [], []), + 'sync_jwks' : IDL.Func([], [], []), + }); +}; +export const init = ({ IDL }) => { return []; }; diff --git a/packages/ssp-backend/src/index.ts b/packages/ssp-backend/src/index.ts new file mode 100644 index 0000000..e089b95 --- /dev/null +++ b/packages/ssp-backend/src/index.ts @@ -0,0 +1,7 @@ +export { + createActor, + ssp_backend, + type CreateActorOptions, +} from "./generated/index"; +export * from "./generated/ssp_backend.did"; +export * from "./utils"; diff --git a/packages/ssp-backend/src/utils.ts b/packages/ssp-backend/src/utils.ts new file mode 100644 index 0000000..4c485a4 --- /dev/null +++ b/packages/ssp-backend/src/utils.ts @@ -0,0 +1,178 @@ +import { + Cbor, + Certificate, + HttpAgent, + compare, + lookupResultToBuffer, + lookup_path, + reconstruct, + type HashTree, + fromHex, + NodeType, +} from "@dfinity/agent"; +import type { CertificateWithId } from "./generated/ssp_backend.did"; +import { Principal } from "@dfinity/principal"; + +export type DecodedCertificate = { + content: { + name: string; + issued_at: string; + sport_category: string; + notes: string | null; + file_uri: string | null; + external_id: string | null; + issuer_full_name: string | null; + issuer_club_name: string | null; + }; + created_at: string; + managed_user_id: string | null; + user_principal: Principal; +}; + +export const optionalToNullable = ( + value: [] | [T] | null | undefined, +): T | null => { + if (!value) { + return null; + } + return value.length === 1 ? value[0] : null; +}; + +export const nullableToOptional = ( + value: T | null | undefined, +): [T] | [] => { + return value ? [value] : []; +}; + +export const parseUint8Array = ( + other: Uint8Array | number[] | Record, +): Uint8Array => { + if (Object.getPrototypeOf(other) === Uint8Array.prototype) { + return other as Uint8Array; + } + if (Array.isArray(other)) { + return new Uint8Array(other); + } + return new Uint8Array(Object.values(other)); +}; + +const parsePrincipal = (principal: string | Principal | object): Principal => { + if (principal instanceof Principal) { + return principal; + } + if (Object.getPrototypeOf(principal) === Uint8Array.prototype) { + return Principal.fromUint8Array(principal as Uint8Array); + } + return Principal.from(JSON.stringify(principal)); +}; + +export const decodeCertificate = ( + certCbor: string | ArrayBuffer, +): DecodedCertificate => { + const cbor = typeof certCbor === "string" ? fromHex(certCbor) : certCbor; + const decoded = Cbor.decode(cbor); + return { + ...decoded, + user_principal: parsePrincipal(decoded.user_principal), + }; +}; + +const uuidToBytes = (uuid: string): Uint8Array => { + // Remove hyphens and convert to lowercase + const hex = uuid.replace(/-/g, "").toLowerCase(); + + // Convert hex string to byte array + const bytes = new Uint8Array(16); + for (let i = 0; i < 16; i++) { + bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); + } + + return bytes; +}; + +const areBuffersEqual = (buf1: ArrayBuffer, buf2: ArrayBuffer): boolean => { + return compare(buf1, buf2) === 0; +}; + +const MAX_CERTIFICATE_AGE_IN_MINUTES = 5; +const SSP_CERTIFICATES_TREE_LABEL = "ssp_certificates"; // same as in the canister + +export const verifyCertificateIntegrity = async ( + data: CertificateWithId, + icCertificate: ArrayBuffer, + icCertificateWitness: ArrayBuffer, + canisterId: string, + icHost: string, + isMainnet: boolean, +): Promise => { + const cid = Principal.fromText(canisterId); + + const agent = await HttpAgent.create({ + host: icHost, + shouldFetchRootKey: !isMainnet, + }); + + let cert; + try { + cert = await Certificate.create({ + certificate: icCertificate, + canisterId: cid, + rootKey: agent.rootKey!, + maxAgeInMinutes: MAX_CERTIFICATE_AGE_IN_MINUTES, + }); + } catch (error) { + console.error("[certification] Error creating certificate:", error); + return false; + } + + const hashTree = Cbor.decode(icCertificateWitness); + const reconstructed = await reconstruct(hashTree); + const witnessLookupResult = cert.lookup([ + "canister", + cid.toUint8Array(), + "certified_data", + ]); + const witness = lookupResultToBuffer(witnessLookupResult); + + if (!witness) { + console.error( + "[certification] Could not find certified data for this canister in the certificate.", + ); + return false; + } + + // First validate that the Tree is as good as the certification. + if (!areBuffersEqual(witness, reconstructed)) { + console.error("[certification] Witness != Tree passed in ic-certification"); + return false; + } + + const certCbor = fromHex(data.certificate_cbor_hex); + const certificateData = decodeCertificate(certCbor); + + // Next, calculate the SHA of the content. + const sha = await reconstruct([NodeType.Leaf, certCbor]); + const path = [ + SSP_CERTIFICATES_TREE_LABEL, + certificateData.user_principal.toUint8Array(), + uuidToBytes(data.id), + ]; + let treeShaLookupResult = lookup_path(path, hashTree); + const treeSha = lookupResultToBuffer(treeShaLookupResult); + + if (!treeSha) { + // The tree returned in the certification header is wrong. Return false. + // We don't throw here, just invalidate the request. + console.error( + "[certification] Invalid Tree in the header. Does not contain path", + path, + ); + return false; + } + + const verified = areBuffersEqual(sha, treeSha as ArrayBuffer); + if (!verified) { + console.error("[certification] SHA does not match tree SHA"); + } + return verified; +}; diff --git a/packages/ssp_backend_types/Cargo.toml b/packages/ssp_backend_types/Cargo.toml new file mode 100644 index 0000000..155680a --- /dev/null +++ b/packages/ssp_backend_types/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "ssp_backend_types" +version = "0.1.0" +edition.workspace = true + +[dependencies] +candid.workspace = true + +serde.workspace = true +serde_bytes.workspace = true +serde_cbor.workspace = true + +[dev-dependencies] +serde_json.workspace = true diff --git a/packages/ssp_backend_types/package.json b/packages/ssp_backend_types/package.json new file mode 100644 index 0000000..1cba9db --- /dev/null +++ b/packages/ssp_backend_types/package.json @@ -0,0 +1,9 @@ +{ + "name": "ssp_backend_types", + "version": "0.1.0", + "private": true, + "scripts": { + "lint": "cargo clippy --all-targets --all-features -- -D warnings", + "test": "cargo test" + } +} diff --git a/packages/ssp_backend_types/src/api/certificate.rs b/packages/ssp_backend_types/src/api/certificate.rs new file mode 100644 index 0000000..b3ade7f --- /dev/null +++ b/packages/ssp_backend_types/src/api/certificate.rs @@ -0,0 +1,173 @@ +use candid::{CandidType, Deserialize, Principal}; +use serde::Serialize; + +use super::ValidateRequest; + +pub const MAX_NAME_CHARS_COUNT: usize = 100; +pub const MAX_SPORT_CATEGORY_CHARS_COUNT: usize = 80; +pub const MAX_NOTES_CHARS_COUNT: usize = 500; +const KB: usize = 1024; +const MB: usize = KB * KB; +pub const MAX_FILE_BYTES_SIZE: usize = MB + (500 * KB); // 1.5 MB +pub const MAX_EXTERNAL_ID_CHARS_COUNT: usize = 100; +pub const MAX_ISSUER_FULL_NAME_CHARS_COUNT: usize = 100; +pub const MAX_ISSUER_CLUB_NAME_CHARS_COUNT: usize = 100; + +#[derive(Debug, CandidType, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CertificateContent { + pub name: String, + pub issued_at: String, + pub sport_category: String, + pub notes: Option, + pub file_uri: Option, + pub external_id: Option, + pub issuer_full_name: Option, + pub issuer_club_name: Option, +} + +#[derive(Debug, CandidType, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct Certificate { + pub user_principal: Principal, + pub created_at: String, + pub content: CertificateContent, + pub managed_user_id: Option, +} + +#[derive(Debug, CandidType, Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct CreateCertificateContentRequest { + pub name: String, + pub issued_at: u64, + pub sport_category: String, + pub notes: Option, + pub file_uri: Option, + pub external_id: Option, + pub issuer_full_name: Option, + pub issuer_club_name: Option, +} + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct CreateCertificateRequest { + pub user_db_id: String, + pub content: CreateCertificateContentRequest, + pub managed_user_db_id: Option, +} + +impl ValidateRequest for CreateCertificateRequest { + fn validate(&self) -> Result<(), String> { + let content = &self.content; + + if content.name.is_empty() { + return Err("Title cannot be empty.".to_string()); + } else if content.name.chars().count() > MAX_NAME_CHARS_COUNT { + return Err(format!( + "Title cannot be longer than {} characters.", + MAX_NAME_CHARS_COUNT + )); + } + + if content.sport_category.is_empty() { + return Err("Sport category cannot be empty.".to_string()); + } else if content.sport_category.chars().count() > MAX_SPORT_CATEGORY_CHARS_COUNT { + return Err(format!( + "Sport category cannot be longer than {} characters.", + MAX_SPORT_CATEGORY_CHARS_COUNT + )); + } + + if let Some(notes) = &content.notes { + if notes.chars().count() > MAX_NOTES_CHARS_COUNT { + return Err(format!( + "Notes cannot be longer than {} characters.", + MAX_NOTES_CHARS_COUNT + )); + } + } + + if let Some(file_uri) = &content.file_uri { + if file_uri.len() > MAX_FILE_BYTES_SIZE { + return Err(format!( + "File bytes cannot be longer than {} bytes.", + MAX_FILE_BYTES_SIZE + )); + } + } + + if let Some(external_id) = &content.external_id { + if external_id.chars().count() > MAX_EXTERNAL_ID_CHARS_COUNT { + return Err(format!( + "External ID cannot be longer than {} characters.", + MAX_EXTERNAL_ID_CHARS_COUNT + )); + } + } + + if let Some(issuer_full_name) = &content.issuer_full_name { + if issuer_full_name.chars().count() > MAX_ISSUER_FULL_NAME_CHARS_COUNT { + return Err(format!( + "Issuer full name cannot be longer than {} characters.", + MAX_ISSUER_FULL_NAME_CHARS_COUNT + )); + } + } + + if let Some(issuer_club_name) = &content.issuer_club_name { + if issuer_club_name.chars().count() > MAX_ISSUER_CLUB_NAME_CHARS_COUNT { + return Err(format!( + "Issuer club name cannot be longer than {} characters.", + MAX_ISSUER_CLUB_NAME_CHARS_COUNT + )); + } + } + + Ok(()) + } +} + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct CreateCertificateResponse { + pub id: String, +} + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct GetUserCertificatesRequest { + pub user_principal: Option, + pub user_db_id: Option, +} + +impl ValidateRequest for GetUserCertificatesRequest { + fn validate(&self) -> Result<(), String> { + if self.user_principal.is_none() && self.user_db_id.is_none() { + return Err("Either user_principal or user_db_id must be provided.".to_string()); + } + + if self.user_principal.is_some() && self.user_db_id.is_some() { + return Err("Only one of user_principal or user_db_id can be provided.".to_string()); + } + + Ok(()) + } +} + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct CertificatePreviewWithId { + pub id: String, + pub name: String, +} + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct GetUserCertificatesResponse { + pub certificates: Vec, +} + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct CertificateWithId { + pub id: String, + pub certificate_cbor_hex: String, +} + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct GetCertificateResponse { + pub certificate: CertificateWithId, + pub ic_certificate: Vec, + pub ic_certificate_witness: Vec, +} diff --git a/packages/ssp_backend_types/src/api/config.rs b/packages/ssp_backend_types/src/api/config.rs new file mode 100644 index 0000000..2677642 --- /dev/null +++ b/packages/ssp_backend_types/src/api/config.rs @@ -0,0 +1,6 @@ +use candid::{CandidType, Deserialize, Principal}; + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct Config { + pub backend_principal: Option, +} diff --git a/packages/ssp_backend_types/src/api/mod.rs b/packages/ssp_backend_types/src/api/mod.rs new file mode 100644 index 0000000..53a89d0 --- /dev/null +++ b/packages/ssp_backend_types/src/api/mod.rs @@ -0,0 +1,45 @@ +mod certificate; +mod config; +mod user; + +pub use certificate::*; +pub use config::*; +pub use user::*; + +/// Implement this trait to validate the request. +/// +/// # Example: +/// +/// ``` +/// use ssp_backend_types::ValidateRequest; +/// +/// struct CreateCertificateRequest { +/// title: String, +/// content: Vec, +/// } +/// +/// impl ValidateRequest for CreateCertificateRequest { +/// fn validate(&self) -> Result<(), String> { +/// if self.title.is_empty() { +/// return Err("Title cannot be empty.".to_string()); +/// } +/// +/// if self.title.chars().count() > 100 { +/// return Err(format!( +/// "Title cannot be longer than {} characters.", +/// 100 +/// )); +/// } +/// +/// if self.content.is_empty() { +/// return Err("Content cannot be empty.".to_string()); +/// } +/// +/// Ok(()) +/// } +/// } +/// ``` +pub trait ValidateRequest { + /// Validate the request, based on its fields. + fn validate(&self) -> Result<(), String>; +} diff --git a/packages/ssp_backend_types/src/api/user.rs b/packages/ssp_backend_types/src/api/user.rs new file mode 100644 index 0000000..9f7e635 --- /dev/null +++ b/packages/ssp_backend_types/src/api/user.rs @@ -0,0 +1,8 @@ +use candid::{CandidType, Deserialize}; + +#[derive(Debug, Clone, CandidType, Deserialize, PartialEq, Eq)] +pub struct User { + pub sub: String, + pub db_id: String, + pub created_at: String, +} diff --git a/packages/ssp_backend_types/src/lib.rs b/packages/ssp_backend_types/src/lib.rs new file mode 100644 index 0000000..4a34330 --- /dev/null +++ b/packages/ssp_backend_types/src/lib.rs @@ -0,0 +1,5 @@ +mod api; +mod types; + +pub use api::*; +pub use types::*; diff --git a/packages/ssp_backend_types/src/types.rs b/packages/ssp_backend_types/src/types.rs new file mode 100644 index 0000000..07ee65e --- /dev/null +++ b/packages/ssp_backend_types/src/types.rs @@ -0,0 +1,113 @@ +use candid::{CandidType, Principal}; +use serde::{Deserialize, Serialize}; +use serde_bytes::ByteBuf; + +pub type PublicKey = ByteBuf; +pub type SessionKey = PublicKey; +pub type UserKey = PublicKey; +pub type Timestamp = u64; // in nanos since epoch +pub type Signature = ByteBuf; + +#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)] +pub struct Delegation { + pub pubkey: PublicKey, + pub expiration: Timestamp, + pub targets: Option>, +} + +#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)] +pub struct SignedDelegation { + pub delegation: Delegation, + pub signature: Signature, +} + +#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)] +pub struct PrepareDelegationResponse { + pub user_key: UserKey, + pub expiration: Timestamp, +} + +#[derive(Clone, Debug, CandidType, Deserialize, PartialEq, Eq)] +pub enum GetDelegationResponse { + #[serde(rename = "signed_delegation")] + SignedDelegation(SignedDelegation), + #[serde(rename = "no_such_delegation")] + NoSuchDelegation, +} + +#[derive(CandidType, Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Auth0JWK { + pub kty: String, + pub r#use: String, + pub n: String, + pub e: String, + pub kid: String, + pub x5t: String, + pub x5c: Vec, + pub alg: String, +} + +#[derive(CandidType, Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct Auth0JWKSet { + pub keys: Vec, +} + +impl Auth0JWKSet { + pub fn find_key(&self, kid: &str) -> Option<&Auth0JWK> { + self.keys.iter().find(|it| it.kid == kid) + } +} + +/// Claims set in the Auth0 action, under the `https://hasura.io/jwt/claims` key. +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +pub struct HasuraJWTClaims { + #[serde(rename = "x-hasura-default-role")] + pub x_hasura_default_role: String, + #[serde(rename = "x-hasura-allowed-roles")] + pub x_hasura_allowed_roles: Vec, + #[serde(rename = "x-hasura-user-id")] + pub x_hasura_user_id: String, +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + const TEST_USER_ID: &str = "bbe2bdb3-c734-4bc7-af3d-068cfc231c12"; + + #[test] + fn test_hasura_jwt_claims_serialization() { + let claims = HasuraJWTClaims { + x_hasura_default_role: "user".to_string(), + x_hasura_allowed_roles: vec!["user".to_string(), "admin".to_string()], + x_hasura_user_id: TEST_USER_ID.to_string(), + }; + + let serialized = serde_json::to_value(claims).unwrap(); + + assert_eq!( + serialized, + json!({ + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user", "admin"], + "x-hasura-user-id": TEST_USER_ID + }) + ); + } + + #[test] + fn test_hasura_jwt_claims_deserialization() { + let json_str = json!({ + "x-hasura-default-role": "user", + "x-hasura-allowed-roles": ["user", "admin"], + "x-hasura-user-id": TEST_USER_ID + }); + + let deserialized: HasuraJWTClaims = serde_json::from_str(&json_str.to_string()).unwrap(); + + assert_eq!(deserialized.x_hasura_default_role, "user"); + assert_eq!(deserialized.x_hasura_allowed_roles, vec!["user", "admin"]); + assert_eq!(deserialized.x_hasura_user_id, TEST_USER_ID); + } +} diff --git a/packages/typescript-config/base.json b/packages/typescript-config/base.json new file mode 100644 index 0000000..0f80cfd --- /dev/null +++ b/packages/typescript-config/base.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Default", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "incremental": false, + "isolatedModules": true, + "lib": ["es2022", "DOM", "DOM.Iterable"], + "module": "NodeNext", + "moduleDetection": "force", + "moduleResolution": "NodeNext", + "noUncheckedIndexedAccess": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022" + } +} diff --git a/packages/typescript-config/nextjs.json b/packages/typescript-config/nextjs.json new file mode 100644 index 0000000..9457147 --- /dev/null +++ b/packages/typescript-config/nextjs.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Next.js", + "extends": "./base.json", + "compilerOptions": { + "plugins": [{ "name": "next" }], + "module": "ESNext", + "moduleResolution": "Bundler", + "allowJs": true, + "jsx": "preserve", + "noEmit": true, + "paths": { + "@web-app/*": ["../../apps/web/*"] + } + } +} diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json new file mode 100644 index 0000000..27c0e60 --- /dev/null +++ b/packages/typescript-config/package.json @@ -0,0 +1,9 @@ +{ + "name": "@repo/typescript-config", + "version": "0.0.0", + "private": true, + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/typescript-config/react-library.json b/packages/typescript-config/react-library.json new file mode 100644 index 0000000..44924d9 --- /dev/null +++ b/packages/typescript-config/react-library.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "React Library", + "extends": "./base.json", + "compilerOptions": { + "jsx": "react-jsx" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..8832c6b --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6901 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@dfinity/agent': + specifier: ^2.1.1 + version: 2.1.1(@dfinity/candid@1.2.0(@dfinity/principal@2.1.1))(@dfinity/principal@2.1.1) + '@dfinity/identity': + specifier: ^2.1.1 + version: 2.1.1(@dfinity/agent@2.1.1(@dfinity/candid@1.2.0(@dfinity/principal@2.1.1))(@dfinity/principal@2.1.1))(@dfinity/principal@2.1.1)(@peculiar/webcrypto@1.4.6) + '@dfinity/principal': + specifier: ^2.1.1 + version: 2.1.1 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + devDependencies: + '@repo/eslint-config': + specifier: workspace:* + version: link:packages/eslint-config + '@repo/typescript-config': + specifier: workspace:* + version: link:packages/typescript-config + '@types/eslint': + specifier: ^8.56.5 + version: 8.56.5 + '@types/node': + specifier: ^20.11.24 + version: 20.14.2 + '@types/react': + specifier: ^18.2.77 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.2.25 + version: 18.3.0 + eslint: + specifier: ^8.57.0 + version: 8.57.0 + prettier: + specifier: ^3.2.5 + version: 3.3.1 + turbo: + specifier: ^2.0.6 + version: 2.0.9 + typescript: + specifier: ^5.3.3 + version: 5.4.5 + vercel: + specifier: ^37.1.0 + version: 37.1.0 + + apps/auth0: + devDependencies: + auth0-deploy-cli: + specifier: ^7.24.1 + version: 7.24.1 + dotenv-cli: + specifier: ^7.4.2 + version: 7.4.2 + + apps/ssp_backend: + dependencies: + ssp_backend_types: + specifier: workspace:* + version: link:../../packages/ssp_backend_types + + packages/config: {} + + packages/eslint-config: + devDependencies: + '@typescript-eslint/eslint-plugin': + specifier: ^7.7.1 + version: 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': + specifier: ^7.7.1 + version: 7.12.0(eslint@8.57.0)(typescript@5.4.5) + '@vercel/style-guide': + specifier: ^6.0.0 + version: 6.0.0(@next/eslint-plugin-next@14.1.1)(eslint@8.57.0)(prettier@3.3.1)(typescript@5.4.5) + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.0) + eslint-config-turbo: + specifier: ^2.0.1 + version: 2.0.1(eslint@8.57.0) + eslint-plugin-only-warn: + specifier: ^1.1.0 + version: 1.1.0 + typescript: + specifier: ^5.3.3 + version: 5.4.5 + + packages/ssp-backend: {} + + packages/ssp_backend_types: {} + + packages/typescript-config: {} + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.24.7': + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.24.7': + resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.24.7': + resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + engines: {node: '>=6.9.0'} + + '@babel/eslint-parser@7.24.1': + resolution: {integrity: sha512-d5guuzMlPeDfZIbpQ8+g1NaCNuAGBBGNECh0HVqz1sjOeVLh2CEaifuOysCH18URW6R7pqXINvf5PaR/dC6jLQ==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 + + '@babel/generator@7.24.7': + resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.24.7': + resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-environment-visitor@7.24.7': + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-function-name@7.24.7': + resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-hoist-variables@7.24.7': + resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.24.7': + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.24.7': + resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-simple-access@7.24.7': + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.24.7': + resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.24.7': + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.24.7': + resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.24.7': + resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.24.7': + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.24.7': + resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.24.7': + resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.24.7': + resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.24.7': + resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.24.7': + resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + engines: {node: '>=6.9.0'} + + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + + '@dfinity/agent@2.1.1': + resolution: {integrity: sha512-9+XPFc9PrXM0kmaqZOOVW3eXBGaWzOjnnbEa55J1NBuDA6+X2jAyE51I62zXr1oFwMeKj7ykOEFd5MOmnMEijA==} + peerDependencies: + '@dfinity/candid': ^2.1.1 + '@dfinity/principal': ^2.1.1 + + '@dfinity/candid@1.2.0': + resolution: {integrity: sha512-L6gV3ODIFC9qNenq3zuRvHrTwH36IM5utVH2wMS5f5eIUeG9fNe+avYLRPBUJwdeX7cM7xhvDgE/m/aN2cZvKQ==} + peerDependencies: + '@dfinity/principal': ^1.2.0 + + '@dfinity/identity@2.1.1': + resolution: {integrity: sha512-Imls2yplJG8KUM4WRW3PHHaC1zJR/xGy6usXpkwJSV3zQVWuxTL0+myM20pTa3qO9RFawPDBtnu1/e14KJZ7Kw==} + peerDependencies: + '@dfinity/agent': ^2.1.1 + '@dfinity/principal': ^2.1.1 + '@peculiar/webcrypto': ^1.4.0 + + '@dfinity/principal@2.1.1': + resolution: {integrity: sha512-XKoQRgL7S7MRPwabY6z2wJ1Mn0WQqe9ZQIMK2wtRq48f5nL1m0rZejf8kR9JsZCNCYK6ss4b09FCRNGC20J8Bg==} + + '@edge-runtime/format@2.2.1': + resolution: {integrity: sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==} + engines: {node: '>=16'} + + '@edge-runtime/node-utils@2.3.0': + resolution: {integrity: sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==} + engines: {node: '>=16'} + + '@edge-runtime/ponyfill@2.4.2': + resolution: {integrity: sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==} + engines: {node: '>=16'} + + '@edge-runtime/primitives@4.1.0': + resolution: {integrity: sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==} + engines: {node: '>=16'} + + '@edge-runtime/vm@3.2.0': + resolution: {integrity: sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==} + engines: {node: '>=16'} + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.10.1': + resolution: {integrity: sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.0': + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@fastify/busboy@2.1.1': + resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} + engines: {node: '>=14'} + + '@humanwhocodes/config-array@0.11.14': + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.2': + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@mapbox/node-pre-gyp@1.0.11': + resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} + hasBin: true + + '@microsoft/tsdoc-config@0.16.2': + resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} + + '@microsoft/tsdoc@0.14.2': + resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + + '@next/eslint-plugin-next@14.1.1': + resolution: {integrity: sha512-NP1WoGFnFLpqqCWgGFjnn/sTwUExdPyjeFKRdQP1X/bL/tjAQ/TXDmYqw6vzGaP5NaZ2u6xzg+N/0nd7fOPOGQ==} + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + + '@noble/curves@1.6.0': + resolution: {integrity: sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.5.0': + resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + engines: {node: ^14.21.3 || >=16} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@peculiar/asn1-schema@2.3.8': + resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==} + + '@peculiar/json-schema@1.1.12': + resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} + engines: {node: '>=8.0.0'} + + '@peculiar/webcrypto@1.4.6': + resolution: {integrity: sha512-YBcMfqNSwn3SujUJvAaySy5tlYbYm6tVt9SKoXu8BaTdKGROiJDgPR3TXpZdAKUfklzm3lRapJEAltiMQtBgZg==} + engines: {node: '>=10.12.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@rollup/pluginutils@4.2.1': + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + + '@rushstack/eslint-patch@1.10.2': + resolution: {integrity: sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==} + + '@sinclair/typebox@0.25.24': + resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} + + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + + '@ts-morph/common@0.11.1': + resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} + + '@tsconfig/node10@1.0.9': + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/body-parser@1.19.5': + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/eslint@8.56.5': + resolution: {integrity: sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw==} + + '@types/estree@1.0.5': + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + + '@types/express-serve-static-core@4.19.3': + resolution: {integrity: sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg==} + + '@types/express@4.17.21': + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + + '@types/http-errors@2.0.4': + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/jsonwebtoken@9.0.6': + resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@16.18.11': + resolution: {integrity: sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==} + + '@types/node@20.14.2': + resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} + + '@types/normalize-package-data@2.4.4': + resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + + '@types/qs@6.9.15': + resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react@18.3.3': + resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + + '@types/send@0.17.4': + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + + '@types/serve-static@1.15.7': + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + + '@typescript-eslint/eslint-plugin@7.12.0': + resolution: {integrity: sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.12.0': + resolution: {integrity: sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/scope-manager@7.12.0': + resolution: {integrity: sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.12.0': + resolution: {integrity: sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/types@7.12.0': + resolution: {integrity: sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@7.12.0': + resolution: {integrity: sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/utils@7.12.0': + resolution: {integrity: sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/visitor-keys@7.12.0': + resolution: {integrity: sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@vercel/build-utils@8.3.6': + resolution: {integrity: sha512-EPwr8tXu41aoXg9QBiF98clu5AHbKtwbp3SeX/W6c8L0fhLwiT+H/s3WDuOL/UMz0TT3B8JAdY4PZioWNEAf6g==} + + '@vercel/error-utils@2.0.2': + resolution: {integrity: sha512-Sj0LFafGpYr6pfCqrQ82X6ukRl5qpmVrHM/191kNYFqkkB9YkjlMAj6QcEsvCG259x4QZ7Tya++0AB85NDPbKQ==} + + '@vercel/fun@1.1.0': + resolution: {integrity: sha512-SpuPAo+MlAYMtcMcC0plx7Tv4Mp7SQhJJj1iIENlOnABL24kxHpL09XLQMGzZIzIW7upR8c3edwgfpRtp+dhVw==} + engines: {node: '>= 10'} + + '@vercel/gatsby-plugin-vercel-analytics@1.0.11': + resolution: {integrity: sha512-iTEA0vY6RBPuEzkwUTVzSHDATo1aF6bdLLspI68mQ/BTbi5UQEGjpjyzdKOVcSYApDtFU6M6vypZ1t4vIEnHvw==} + + '@vercel/gatsby-plugin-vercel-builder@2.0.40': + resolution: {integrity: sha512-j3pZch8Qk2jfM87+dXX+xZXtdD+T2cMOmHhQWzO4oX3issxfBkkk2mAmY4o3Cxry5Xxe5iDQo6VsvY2IrWpCuA==} + + '@vercel/go@3.1.1': + resolution: {integrity: sha512-mrzomNYltxkjvtUmaYry5YEyvwTz6c/QQHE5Gr/pPGRIniUiP6T6OFOJ49RBN7e6pRXaNzHPVuidiuBhvHh5+Q==} + + '@vercel/hydrogen@1.0.4': + resolution: {integrity: sha512-Sc0lpmI/J6O3o2cL75k8klL7ir2gi6kYI92O5+MrR3hh4fwz/atUIL9UWsTGuFjKTm69VAoJrmn3VKf0/0SGLw==} + + '@vercel/next@4.3.6': + resolution: {integrity: sha512-qUHp79xX07qYtz7DGSogyWgEMrf+eu/IGV/92YnVA1xzDBogIFc8XFvMlN8QwDrsWWsR+I2eMSiGD+P8znlsaA==} + + '@vercel/nft@0.27.3': + resolution: {integrity: sha512-oySTdDSzUAFDXpsSLk9Q943o+/Yu/+TCFxnehpFQEf/3khi2stMpTHPVNwFdvZq/Z4Ky93lE+MGHpXCRpMkSCA==} + engines: {node: '>=16'} + hasBin: true + + '@vercel/node@3.2.8': + resolution: {integrity: sha512-mINg3ab1FHIqupZlLVpmCvyqGtkafnyNesgs7ZoCbNxqbb4ZrHtPj1kHv9cvTrFlDkFapkV/Ez8nbSsHeAxtOw==} + + '@vercel/python@4.3.1': + resolution: {integrity: sha512-pWRApBwUsAQJS8oZ7eKMiaBGbYJO71qw2CZqDFvkTj34FNBZtNIUcWSmqGfJJY5m2pU/9wt8z1CnKIyT9dstog==} + + '@vercel/redwood@2.1.3': + resolution: {integrity: sha512-lpsdQSHS2hvSX29/rJNm4q38dVXKstS4MVg875KE6zyXpACwviXuet0Cadyv0E60w7f2B6Ra+nJMpwKz6oJ5xg==} + + '@vercel/remix-builder@2.2.6': + resolution: {integrity: sha512-LOFad9G+CZuq2TNvbT5A03+c437YPy6/J1hHBGMWS6rQ/PWHQSJdEUga9RwTavWoWpCCnrVpMM115EgMKk8JBA==} + + '@vercel/routing-utils@3.1.0': + resolution: {integrity: sha512-Ci5xTjVTJY/JLZXpCXpLehMft97i9fH34nu9PGav6DtwkVUF6TOPX86U0W0niQjMZ5n6/ZP0BwcJK2LOozKaGw==} + + '@vercel/ruby@2.1.0': + resolution: {integrity: sha512-UZYwlSEEfVnfzTmgkD+kxex9/gkZGt7unOWNyWFN7V/ZnZSsGBUgv6hXLnwejdRi3EztgRQEBd1kUKlXdIeC0Q==} + + '@vercel/static-build@2.5.18': + resolution: {integrity: sha512-UgF/wrx5j07PMidvCVTjlTw1C47Ly+tpWihQyAH42xzf9BHtM+8S9UwOPdM5GYhtBuq2fmpPxFi9DCQ5sg4g7Q==} + + '@vercel/static-config@3.0.0': + resolution: {integrity: sha512-2qtvcBJ1bGY0dYGYh3iM7yGKkk971FujLEDXzuW5wcZsPr1GSEjO/w2iSr3qve6nDDtBImsGoDEnus5FI4+fIw==} + + '@vercel/style-guide@6.0.0': + resolution: {integrity: sha512-tu0wFINGz91EPwaT5VjSqUwbvCY9pvLach7SPG4XyfJKPU9Vku2TFa6+AyzJ4oroGbo9fK+TQhIFHrnFl0nCdg==} + engines: {node: '>=18.18'} + peerDependencies: + '@next/eslint-plugin-next': '>=12.3.0 <15.0.0-0' + eslint: '>=8.48.0 <9' + prettier: '>=3.0.0 <4' + typescript: '>=4.8.0 <6' + peerDependenciesMeta: + '@next/eslint-plugin-next': + optional: true + eslint: + optional: true + prettier: + optional: true + typescript: + optional: true + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + + acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.6.3: + resolution: {integrity: sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + arg@4.1.0: + resolution: {integrity: sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array-includes@3.1.7: + resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.3: + resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + array.prototype.toreversed@1.1.2: + resolution: {integrity: sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==} + + array.prototype.tosorted@1.1.3: + resolution: {integrity: sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + asn1js@3.0.5: + resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==} + engines: {node: '>=12.0.0'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + async-listen@1.2.0: + resolution: {integrity: sha512-CcEtRh/oc9Jc4uWeUwdpG/+Mb2YUHKmdaTf0gUr7Wa+bfp4xx70HOb3RuSTJMvqKNB1TkdTfjLdrcz2X4rkkZA==} + + async-listen@3.0.0: + resolution: {integrity: sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==} + engines: {node: '>= 14'} + + async-listen@3.0.1: + resolution: {integrity: sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==} + engines: {node: '>= 14'} + + async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + + async@3.2.5: + resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + auth0-deploy-cli@7.24.1: + resolution: {integrity: sha512-pbKNZcoilMrDekrJZP4BE4Rb6LRhnkGDb8/RKW1QK3IjTb7I+H9/1DgZOzb0J9UFuI0cP3zrfCVfUW+e/Zg/ZQ==} + hasBin: true + + auth0@3.7.2: + resolution: {integrity: sha512-8XwCi5e0CC08A4+l3eTmx/arXjGUlXrLd6/LUBvQfedmI8w4jiNc9pd7dyBUgR00EzhcbcrdNEQo5jkU3hMIJg==} + engines: {node: '>=14'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.7.0: + resolution: {integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==} + engines: {node: '>=4'} + + axios@1.7.4: + resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} + + axobject-query@3.2.1: + resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-arraybuffer@0.2.0: + resolution: {integrity: sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==} + engines: {node: '>= 0.6.0'} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bignumber.js@9.1.2: + resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + + borc@2.1.2: + resolution: {integrity: sha512-Sy9eoUi4OiKzq7VovMn246iTo17kzuyHJKomCfpWMlI6RpfN1gk95w7d7gH264nApVLg0HZfcpz62/g4VH1Y4w==} + engines: {node: '>=4'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + + bytes@3.1.0: + resolution: {integrity: sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==} + engines: {node: '>= 0.8'} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camel-case@1.2.2: + resolution: {integrity: sha512-rUug78lL8mqStaLehmH2F0LxMJ2TM9fnPFxb+gFkgyUjUM/1o2wKTQtalypHnkb2cFwH/DENBw7YEAOYLgSMxQ==} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001629: + resolution: {integrity: sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + change-case@2.3.1: + resolution: {integrity: sha512-3HE5jrTqqn9jeKzD0+yWi7FU4OMicLbwB57ph4bpwEn5jGi3hZug5WjZjnBD2RY7YyTKAAck86ACfShXUWJKLg==} + + chokidar@3.3.1: + resolution: {integrity: sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==} + engines: {node: '>= 8.10.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + ci-info@4.0.0: + resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} + engines: {node: '>=8'} + + cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + + clean-regexp@1.0.0: + resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} + engines: {node: '>=4'} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + code-block-writer@10.1.1: + resolution: {integrity: sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + constant-case@1.1.2: + resolution: {integrity: sha512-FQ/HuOuSnX6nIF8OnofRWj+KnOpGAHXQpOKHmsL1sAnuLwu6r5mHGK+mJc0SkHkbmNfcU/SauqXLTEOL1JQfJA==} + + content-type@1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + + convert-hrtime@3.0.0: + resolution: {integrity: sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==} + engines: {node: '>=8'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + + core-js-compat@3.37.1: + resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} + + core-js@3.38.1: + resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.1.1: + resolution: {integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==} + deprecated: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797) + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@3.3.0: + resolution: {integrity: sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + delimit-stream@0.1.0: + resolution: {integrity: sha512-a02fiQ7poS5CnjiJBAsjGLPp5EwVoGHNeu9sziBd9huppRfsAFIpv5zNLv0V1gbop53ilngAf5Kf331AwcoRBQ==} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-indent@7.0.1: + resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==} + engines: {node: '>=12.20'} + + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + detect-newline@4.0.1: + resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dot-case@1.1.2: + resolution: {integrity: sha512-NzEIt12UjECXi6JZ/R/nBey6EE1qCN0yUTEFaPIaKW0AcOEwlKqujtcJVbtSfLNnj3CDoXLQyli79vAaqohyvw==} + + dot-prop@5.3.0: + resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + engines: {node: '>=8'} + + dotenv-cli@7.4.2: + resolution: {integrity: sha512-SbUj8l61zIbzyhIbg0FwPJq6+wjbzdn9oEtozQpZ6kW2ihCcapKVZj49oCT3oPM+mgQm+itgvUQcG5szxVrZTA==} + hasBin: true + + dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + + dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + edge-runtime@2.5.9: + resolution: {integrity: sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==} + engines: {node: '>=16'} + hasBin: true + + electron-to-chromium@1.4.791: + resolution: {integrity: sha512-6FlqP0NSWvxFf1v+gHu+LCn5wjr1pmkj5nPr7BsxPnj41EDR4EWhK/KmQN0ytHUqgTR1lkpHRYxvHBLZFQtkKw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + + end-of-stream@1.1.0: + resolution: {integrity: sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ==} + + end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + + enhanced-resolve@5.15.0: + resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} + engines: {node: '>=10.13.0'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.0.19: + resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.4.1: + resolution: {integrity: sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + esbuild-android-64@0.14.47: + resolution: {integrity: sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + esbuild-android-arm64@0.14.47: + resolution: {integrity: sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + esbuild-darwin-64@0.14.47: + resolution: {integrity: sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + esbuild-darwin-arm64@0.14.47: + resolution: {integrity: sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + esbuild-freebsd-64@0.14.47: + resolution: {integrity: sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + esbuild-freebsd-arm64@0.14.47: + resolution: {integrity: sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + esbuild-linux-32@0.14.47: + resolution: {integrity: sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + esbuild-linux-64@0.14.47: + resolution: {integrity: sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + esbuild-linux-arm64@0.14.47: + resolution: {integrity: sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + esbuild-linux-arm@0.14.47: + resolution: {integrity: sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + esbuild-linux-mips64le@0.14.47: + resolution: {integrity: sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + esbuild-linux-ppc64le@0.14.47: + resolution: {integrity: sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + esbuild-linux-riscv64@0.14.47: + resolution: {integrity: sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + esbuild-linux-s390x@0.14.47: + resolution: {integrity: sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + esbuild-netbsd-64@0.14.47: + resolution: {integrity: sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + esbuild-openbsd-64@0.14.47: + resolution: {integrity: sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + esbuild-sunos-64@0.14.47: + resolution: {integrity: sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + esbuild-windows-32@0.14.47: + resolution: {integrity: sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + esbuild-windows-64@0.14.47: + resolution: {integrity: sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + esbuild-windows-arm64@0.14.47: + resolution: {integrity: sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + esbuild@0.14.47: + resolution: {integrity: sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-config-turbo@2.0.1: + resolution: {integrity: sha512-Z/TjALEtWvFmi7c2pszuyU8VMrp7KH1q3I4eNBLzlit5kybYDJhacUDYRDyIdGqXg6XljdyDpxNIOKQByScknw==} + peerDependencies: + eslint: '>6.6.0' + + eslint-import-resolver-alias@1.1.2: + resolution: {integrity: sha512-WdviM1Eu834zsfjHtcGHtGfcu+F30Od3V7I9Fi57uhBEwPkjDcii7/yW8jAT+gOhn4P/vOxxNAXbFAKsrrc15w==} + engines: {node: '>= 4'} + peerDependencies: + eslint-plugin-import: '>=1.4.0' + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.6.1: + resolution: {integrity: sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + + eslint-module-utils@2.8.0: + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-eslint-comments@3.2.0: + resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} + engines: {node: '>=6.5.0'} + peerDependencies: + eslint: '>=4.19.1' + + eslint-plugin-import@2.29.1: + resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jest@27.9.0: + resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + + eslint-plugin-jsx-a11y@6.8.0: + resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-only-warn@1.1.0: + resolution: {integrity: sha512-2tktqUAT+Q3hCAU0iSf4xAN1k9zOpjK5WO8104mB0rT/dGhOa09582HN5HlbxNbPRZ0THV7nLGvzugcNOSjzfA==} + engines: {node: '>=6'} + + eslint-plugin-playwright@1.6.0: + resolution: {integrity: sha512-tI1E/EDbHT4Fx5KvukUG3RTIT0gk44gvTP8bNwxLCFsUXVM98ZJG5zWU6Om5JOzH9FrmN4AhMu/UKyEsu0ZoDA==} + engines: {node: '>=16.6.0'} + peerDependencies: + eslint: '>=8.40.0' + eslint-plugin-jest: '>=25' + peerDependenciesMeta: + eslint-plugin-jest: + optional: true + + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react@7.34.1: + resolution: {integrity: sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-testing-library@6.2.2: + resolution: {integrity: sha512-1E94YOTUDnOjSLyvOwmbVDzQi/WkKm3WVrMXu6SmBr6DN95xTGZmI6HJ/eOkSXh/DlheRsxaPsJvZByDBhWLVQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0, npm: '>=6'} + peerDependencies: + eslint: ^7.5.0 || ^8.0.0 + + eslint-plugin-tsdoc@0.2.17: + resolution: {integrity: sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==} + + eslint-plugin-turbo@2.0.1: + resolution: {integrity: sha512-qe3WlGQXVKIZyWlONjvd3+YbtptK6KvfoLb6TrVzkT+mNYbDpjjbGZ3z12Oj0kUNW31n5Me3EVnrgxeyyw1V3g==} + peerDependencies: + eslint: '>6.6.0' + + eslint-plugin-unicorn@51.0.1: + resolution: {integrity: sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==} + engines: {node: '>=16'} + peerDependencies: + eslint: '>=8.56.0' + + eslint-plugin-vitest@0.3.26: + resolution: {integrity: sha512-oxe5JSPgRjco8caVLTh7Ti8PxpwJdhSV0hTQAmkFcNcmy/9DnqLB/oNVRA11RmVRP//2+jIIT6JuBEcpW3obYg==} + engines: {node: ^18.0.0 || >= 20.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': '*' + eslint: '>=8.0.0' + vitest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + vitest: + optional: true + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + events-intercept@2.0.0: + resolution: {integrity: sha512-blk1va0zol9QOrdZt0rFXo5KMkNPVSp92Eju/Qz8THwKWKRKeE0T8Br/1aW6+Edkyq9xHYgYxn2QtOnUKPUp+Q==} + + execa@3.2.0: + resolution: {integrity: sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw==} + engines: {node: ^8.12.0 || >=9.7.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + + follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + + form-data@3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + + formidable@2.1.2: + resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@11.1.0: + resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==} + engines: {node: '>=14.14'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-minipass@1.2.7: + resolution: {integrity: sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.1.3: + resolution: {integrity: sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + deprecated: '"Please update to latest v2.3 or v2.2"' + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + deprecated: This package is no longer supported. + + generic-pool@3.4.2: + resolution: {integrity: sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==} + engines: {node: '>= 4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-stdin@9.0.0: + resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} + engines: {node: '>=12'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + + git-hooks-list@3.1.0: + resolution: {integrity: sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + global-agent@2.2.0: + resolution: {integrity: sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==} + engines: {node: '>=10.0'} + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hexoid@1.0.0: + resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} + engines: {node: '>=8'} + + hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + + http-errors@1.4.0: + resolution: {integrity: sha512-oLjPqve1tuOl5aRhv8GK5eHpqP1C9fb+Ol+XTLjKfLltE44zdDbEdjPSbU7Ch5rSNsVFqZn97SrMmZLdu1/YMw==} + engines: {node: '>= 0.6'} + + http-errors@1.7.3: + resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==} + engines: {node: '>= 0.6'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.1: + resolution: {integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@2.0.0: + resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} + engines: {node: '>=10'} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-lower-case@1.1.3: + resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-obj@2.0.0: + resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} + engines: {node: '>=8'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-upper-case@1.1.2: + resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.3: + resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} + engines: {node: '>= 0.4'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iso-url@0.4.7: + resolution: {integrity: sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog==} + engines: {node: '>=10'} + + iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + jose@4.15.5: + resolution: {integrity: sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + + jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-to-ts@1.6.4: + resolution: {integrity: sha512-pR4yQ9DHz6itqswtHCm26mw45FSNfQ9rEQjosaZErhn5J3J2sIViQiz8rDaezjKAhFGpmsoczYVBgGHzFw/stA==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json-text-sequence@0.1.1: + resolution: {integrity: sha512-L3mEegEWHRekSHjc7+sc8eJhba9Clq1PZ8kMkzf8OxElhXc8O4TS5MwcVlj9aEbm5dr81N90WHC5nAz3UO971w==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jwks-rsa@3.1.0: + resolution: {integrity: sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==} + engines: {node: '>=14'} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + + language-subtag-registry@0.3.22: + resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + limiter@1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + logform@2.6.1: + resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==} + engines: {node: '>= 12.0.0'} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lower-case-first@1.0.2: + resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==} + + lower-case@1.1.4: + resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} + + lru-cache@10.2.2: + resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} + engines: {node: 14 || >=16.14} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-memoizer@2.3.0: + resolution: {integrity: sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==} + + make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micro@9.3.5-canary.3: + resolution: {integrity: sha512-viYIo9PefV+w9dvoIBh1gI44Mvx1BOk67B4BpC2QK77qdY0xZF0Q+vWLt/BII6cLkIc8rLmSIcJaB/OrXXKe1g==} + engines: {node: '>= 8.0.0'} + hasBin: true + + micromatch@4.0.7: + resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@2.9.0: + resolution: {integrity: sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@1.3.3: + resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.1: + resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==} + + ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + nconf@0.12.1: + resolution: {integrity: sha512-p2cfF+B3XXacQdswUYWZ0w6Vld0832A/tuqjLBu3H1sfUcby4N2oVbGhyuCkZv+t3iY3aiFEj7gZGqax9Q2c1w==} + engines: {node: '>= 0.4.0'} + + next-tick@1.1.0: + resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + + node-fetch@2.6.7: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@2.6.9: + resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.1: + resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} + hasBin: true + + node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true + + normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} + deprecated: This package is no longer supported. + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.7: + resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.7: + resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.1: + resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + + object.hasown@1.1.3: + resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} + + object.values@1.1.7: + resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + engines: {node: '>= 0.4'} + + once@1.3.3: + resolution: {integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + os-paths@4.4.0: + resolution: {integrity: sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg==} + engines: {node: '>= 6.0'} + + p-defer@1.0.0: + resolution: {integrity: sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==} + engines: {node: '>=4'} + + p-defer@3.0.0: + resolution: {integrity: sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==} + engines: {node: '>=8'} + + p-finally@2.0.1: + resolution: {integrity: sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + param-case@1.1.2: + resolution: {integrity: sha512-gksk6zeZQxwBm1AHsKh+XDFsTGf1LvdZSkkpSIkfDtzW+EQj/P2PBgNb3Cs0Y9Xxqmbciv2JZe3fWU6Xbher+Q==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-ms@2.1.0: + resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} + engines: {node: '>=6'} + + pascal-case@1.1.2: + resolution: {integrity: sha512-QWlbdQHdKWlcyTEuv/M0noJtlCa7qTmg5QFAqhx5X9xjAfCU1kXucL+rcOmd2HliESuRLIOz8521RAW/yhuQog==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-case@1.1.2: + resolution: {integrity: sha512-2snAGA6xVRqTuTPa40bn0iEpYtVK6gEqeyS/63dqpm5pGlesOv6EmRcnB9Rr6eAnAC2Wqlbz0tqgJZryttxhxg==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-match@1.2.4: + resolution: {integrity: sha512-UWlehEdqu36jmh4h5CWJ7tARp1OEVKGHKm6+dg9qMq5RKUTV5WJrGgaZ3dN2m7WFAXDbjlHzvJvL/IUpy84Ktw==} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@1.8.0: + resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==} + + path-to-regexp@6.1.0: + resolution: {integrity: sha512-h9DqehX3zZZDCEm+xbfU0ZmwCGFCAAraPJWMXJ4+v32NjZJilVg3k1TcKsRgIb8IQ/izZSaydDc1OhJCZvs2Dw==} + + path-to-regexp@6.2.1: + resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-plugin-packagejson@2.5.0: + resolution: {integrity: sha512-6XkH3rpin5QEQodBSVNg+rBo4r91g/1mCaRwS1YGdQJZ6jwqrg2UchBsIG9tpS1yK1kNBvOt84OILsX8uHzBGg==} + peerDependencies: + prettier: '>= 1.16.0' + peerDependenciesMeta: + prettier: + optional: true + + prettier@3.3.1: + resolution: {integrity: sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==} + engines: {node: '>=14'} + hasBin: true + + pretty-ms@7.0.1: + resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} + engines: {node: '>=10'} + + promise-batcher@1.1.1: + resolution: {integrity: sha512-DtGBp8OQlAtALDFoVphyAz6Vn1c0GyelKU5smpMQEXpEAcWWxY3fC5JT0AkpWqrljvJrhC6zVdWbK/jx6NNG2A==} + engines: {node: '>=8'} + + promise-pool-executor@1.1.1: + resolution: {integrity: sha512-WZTGr7E8tiW93QoSRe0+arBIrJ13m7yKov/vyWqP8nkME/OjfzZgmSJjLtcDHd6VVz2LdSoAHZLmDfis8hJd1w==} + engines: {node: '>=5.0.0'} + + promisepipe@3.0.0: + resolution: {integrity: sha512-V6TbZDJ/ZswevgkDNpGt/YqNCiZP9ASfgU+p83uJE6NrGtvSGoOcHLiDCqkMs2+yg7F5qHdLV8d0aS8O26G/KA==} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + + punycode@2.3.0: + resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + engines: {node: '>=6'} + + pvtsutils@1.3.5: + resolution: {integrity: sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==} + + pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + + qs@6.12.1: + resolution: {integrity: sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + raw-body@2.4.1: + resolution: {integrity: sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==} + engines: {node: '>= 0.8'} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-pkg-up@7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + + read-pkg@5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.3.0: + resolution: {integrity: sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==} + engines: {node: '>=8.10.0'} + + reflect.getprototypeof@1.0.4: + resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} + engines: {node: '>= 0.4'} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regexp-tree@0.1.27: + resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} + hasBin: true + + regexp.prototype.flags@1.5.2: + resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + engines: {node: '>= 0.4'} + + regjsparser@0.10.0: + resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.19.0: + resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + rest-facade@1.16.4: + resolution: {integrity: sha512-EeQm4TMYFAvEw/6wV0OyjerdR8V2cThnmXuPCmRWSrwG6p2fZw9ZkzMIYy33OpdnvHCoGHggKOly7J6Nu3nsAQ==} + peerDependencies: + superagent-proxy: ^3.0.0 + peerDependenciesMeta: + superagent-proxy: + optional: true + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sanitize-filename@1.6.3: + resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + secure-keys@1.0.0: + resolution: {integrity: sha512-nZi59hW3Sl5P3+wOO89eHBAAGwmCPd2aE1+dLZV5MO+ItQctIvAqihzaAXIQhvtH4KJPxM080HsnqltR2y8cWg==} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.3.5: + resolution: {integrity: sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==} + engines: {node: '>=10'} + hasBin: true + + semver@7.6.2: + resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + engines: {node: '>=10'} + hasBin: true + + sentence-case@1.1.3: + resolution: {integrity: sha512-laa/UDTPXsrQnoN/Kc8ZO7gTeEjMsuPiDgUCk9N0iINRZvqAMCTXjGl8+tD27op1eF/JHbdUlEUmovDh6AX7sA==} + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + + setprototypeof@1.1.1: + resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.0.2: + resolution: {integrity: sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==} + engines: {node: '>=14'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-cbor@0.4.1: + resolution: {integrity: sha512-rijcxtwx2b4Bje3sqeIqw5EeW7UlOIC4YfOdwqIKacpvRQ/D78bWg/4/0m5e0U91oKvlGh7LlJuZCu07ISCC7w==} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + + snake-case@1.1.2: + resolution: {integrity: sha512-oapUKC+qulnUIN+/O7Tbl2msi9PQvJeivGN9RNbygxzI2EOY0gA96i8BJLYnGUWSLGcYtyW4YYqnGTZEySU/gg==} + + sort-object-keys@1.1.3: + resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==} + + sort-package-json@2.10.0: + resolution: {integrity: sha512-MYecfvObMwJjjJskhxYfuOADkXp1ZMMnCFC8yhp+9HDsk7HhR336hd7eiBs96lTXfiqmUNI+WQCeCMRBhl251g==} + hasBin: true + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.16: + resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + stat-mode@0.3.0: + resolution: {integrity: sha512-QjMLR0A3WwFY2aZdV0okfFEJB5TRjkggXZjxP3A1RsWsNHNu3YPv8btmtc6iCFZ0Rul3FE93OYogvhOUClU+ng==} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + stream-to-array@2.3.0: + resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} + + stream-to-promise@2.2.0: + resolution: {integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.matchall@4.0.10: + resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + superagent@7.1.6: + resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} + engines: {node: '>=6.4.0 <13 || >=14'} + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swap-case@1.1.2: + resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==} + + synckit@0.9.0: + resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} + engines: {node: ^14.18.0 || >=16.0.0} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + tar@4.4.18: + resolution: {integrity: sha512-ZuOtqqmkV9RE1+4odd+MhBpibmCxNP6PJhH/h2OqNuotTX7/XHPZQJv2pKvWMplFH9SIZZhitehh6vBH6LO8Pg==} + engines: {node: '>=4.5'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + time-span@4.0.0: + resolution: {integrity: sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==} + engines: {node: '>=10'} + + title-case@1.1.2: + resolution: {integrity: sha512-xYbo5Um5MBgn24xJSK+x5hZ8ehuGXTVhgx32KJCThHRHwpyIb1lmABi1DH5VvN9E7rNEquPjz//rF/tZQd7mjQ==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.0: + resolution: {integrity: sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + + truncate-utf8-bytes@1.0.2: + resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-morph@12.0.0: + resolution: {integrity: sha512-VHC8XgU2fFW7yO1f/b3mxKDje1vmyzFXHWzOYmKEkCEwcLjDtbdLgBQviqj4ZwP4MJkQtRo6Ha2I29lq/B+VxA==} + + ts-node@10.9.1: + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + ts-toolbelt@6.15.5: + resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.6.3: + resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + + turbo-darwin-64@2.0.9: + resolution: {integrity: sha512-owlGsOaExuVGBUfrnJwjkL1BWlvefjSKczEAcpLx4BI7Oh6ttakOi+JyomkPkFlYElRpjbvlR2gP8WIn6M/+xQ==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.0.9: + resolution: {integrity: sha512-XAXkKkePth5ZPPE/9G9tTnPQx0C8UTkGWmNGYkpmGgRr8NedW+HrPsi9N0HcjzzIH9A4TpNYvtiV+WcwdaEjKA==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.0.9: + resolution: {integrity: sha512-l9wSgEjrCFM1aG16zItBsZ206ZlhSSx1owB8Cgskfv0XyIXRGHRkluihiaxkp+UeU5WoEfz4EN5toc+ICA0q0w==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.0.9: + resolution: {integrity: sha512-gRnjxXRne18B27SwxXMqL3fJu7jw/8kBrOBTBNRSmZZiG1Uu3nbnP7b4lgrA/bCku6C0Wligwqurvtpq6+nFHA==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.0.9: + resolution: {integrity: sha512-ZVo0apxUvaRq4Vm1qhsfqKKhtRgReYlBVf9MQvVU1O9AoyydEQvLDO1ryqpXDZWpcHoFxHAQc9msjAMtE5K2lA==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.0.9: + resolution: {integrity: sha512-sGRz7c5Pey6y7y9OKi8ypbWNuIRPF9y8xcMqL56OZifSUSo+X2EOsOleR9MKxQXVaqHPGOUKWsE6y8hxBi9pag==} + cpu: [arm64] + os: [win32] + + turbo@2.0.9: + resolution: {integrity: sha512-QaLaUL1CqblSKKPgLrFW3lZWkWG4pGBQNW+q1ScJB5v1D/nFWtsrD/yZljW/bdawg90ihi4/ftQJ3h6fz1FamA==} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + + type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + + uid-promise@1.0.0: + resolution: {integrity: sha512-R8375j0qwXyIu/7R0tjdF06/sElHqbmdmWC9M2qQHpEVbvE4I5+38KJI7LUUmQMp7NVq4tKHiBMkT0NFM453Ig==} + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici@5.28.4: + resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} + engines: {node: '>=14.0'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.0.16: + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + upper-case-first@1.1.2: + resolution: {integrity: sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==} + + upper-case@1.1.3: + resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + utf8-byte-length@1.0.5: + resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@3.3.2: + resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} + deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. + hasBin: true + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + vercel@37.1.0: + resolution: {integrity: sha512-FzXjzbwRBUVD7lBxdvX5x4u7RhNFp+gEHhmvEHOG/t2CHrFadCd7hLfItAccQy3r3BuPvA+XHhuVLeYqJTfoSA==} + engines: {node: '>= 16'} + hasBin: true + + web-vitals@0.2.4: + resolution: {integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==} + + webcrypto-core@1.7.9: + resolution: {integrity: sha512-FE+a4PPkOmBbgNDIyRmcHhgXn+2ClRl3JzJdDu/P4+B8y81LqKe6RAsI9b3lAOHe1T1BMkSjsRHTYRikImZnVA==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + winston-transport@4.7.1: + resolution: {integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==} + engines: {node: '>= 12.0.0'} + + winston@3.14.2: + resolution: {integrity: sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==} + engines: {node: '>= 12.0.0'} + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xdg-app-paths@5.1.0: + resolution: {integrity: sha512-RAQ3WkPf4KTU1A8RtFx3gWywzVKe00tfOPFfl2NDGqbIFENQO4kqAJp7mhQjNj/33W5x5hiWWUdyfPq/5SU3QA==} + engines: {node: '>=6'} + + xdg-portable@7.3.0: + resolution: {integrity: sha512-sqMMuL1rc0FmMBOzCpd0yuy9trqF2yTTVe+E9ogwCSWQCdDEtQUwrZPT6AxqtsFGRNxycgncbP/xmOOSPw5ZUw==} + engines: {node: '>= 6.0'} + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yauzl-clone@1.0.4: + resolution: {integrity: sha512-igM2RRCf3k8TvZoxR2oguuw4z1xasOnA31joCqHIyLkeWrvAc2Jgay5ISQ2ZplinkoGaJ6orCz56Ey456c5ESA==} + engines: {node: '>=6'} + + yauzl-promise@2.1.3: + resolution: {integrity: sha512-A1pf6fzh6eYkK0L4Qp7g9jzJSDrM6nN0bOn5T0IbY4Yo3w+YkWlHFkJP7mzknMXjqusHFHlKsK2N+4OLsK2MRA==} + engines: {node: '>=6'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.24.7': + dependencies: + '@babel/highlight': 7.24.7 + picocolors: 1.0.1 + + '@babel/compat-data@7.24.7': {} + + '@babel/core@7.24.7': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) + '@babel/helpers': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + convert-source-map: 2.0.0 + debug: 4.3.5 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/eslint-parser@7.24.1(@babel/core@7.24.7)(eslint@8.57.0)': + dependencies: + '@babel/core': 7.24.7 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 8.57.0 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + + '@babel/generator@7.24.7': + dependencies: + '@babel/types': 7.24.7 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + + '@babel/helper-compilation-targets@7.24.7': + dependencies: + '@babel/compat-data': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + browserslist: 4.23.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-environment-visitor@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-function-name@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/helper-hoist-variables@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-module-imports@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': + dependencies: + '@babel/core': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-simple-access@7.24.7': + dependencies: + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/helper-string-parser@7.24.7': {} + + '@babel/helper-validator-identifier@7.24.7': {} + + '@babel/helper-validator-option@7.24.7': {} + + '@babel/helpers@7.24.7': + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/highlight@7.24.7': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.1 + + '@babel/parser@7.24.7': + dependencies: + '@babel/types': 7.24.7 + + '@babel/runtime@7.24.7': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/traverse@7.24.7': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + debug: 4.3.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.24.7': + dependencies: + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + + '@colors/colors@1.6.0': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + + '@dfinity/agent@2.1.1(@dfinity/candid@1.2.0(@dfinity/principal@2.1.1))(@dfinity/principal@2.1.1)': + dependencies: + '@dfinity/candid': 1.2.0(@dfinity/principal@2.1.1) + '@dfinity/principal': 2.1.1 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + base64-arraybuffer: 0.2.0 + borc: 2.1.2 + buffer: 6.0.3 + simple-cbor: 0.4.1 + + '@dfinity/candid@1.2.0(@dfinity/principal@2.1.1)': + dependencies: + '@dfinity/principal': 2.1.1 + + '@dfinity/identity@2.1.1(@dfinity/agent@2.1.1(@dfinity/candid@1.2.0(@dfinity/principal@2.1.1))(@dfinity/principal@2.1.1))(@dfinity/principal@2.1.1)(@peculiar/webcrypto@1.4.6)': + dependencies: + '@dfinity/agent': 2.1.1(@dfinity/candid@1.2.0(@dfinity/principal@2.1.1))(@dfinity/principal@2.1.1) + '@dfinity/principal': 2.1.1 + '@noble/curves': 1.6.0 + '@noble/hashes': 1.5.0 + '@peculiar/webcrypto': 1.4.6 + borc: 2.1.2 + + '@dfinity/principal@2.1.1': + dependencies: + '@noble/hashes': 1.5.0 + + '@edge-runtime/format@2.2.1': {} + + '@edge-runtime/node-utils@2.3.0': {} + + '@edge-runtime/ponyfill@2.4.2': {} + + '@edge-runtime/primitives@4.1.0': {} + + '@edge-runtime/vm@3.2.0': + dependencies: + '@edge-runtime/primitives': 4.1.0 + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.10.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.3.5 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.0': {} + + '@fastify/busboy@2.1.1': {} + + '@humanwhocodes/config-array@0.11.14': + dependencies: + '@humanwhocodes/object-schema': 2.0.2 + debug: 4.3.5 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.2': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + optional: true + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.4.15': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.3 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.6.2 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + + '@microsoft/tsdoc-config@0.16.2': + dependencies: + '@microsoft/tsdoc': 0.14.2 + ajv: 6.12.6 + jju: 1.4.0 + resolve: 1.19.0 + + '@microsoft/tsdoc@0.14.2': {} + + '@next/eslint-plugin-next@14.1.1': + dependencies: + glob: 10.3.10 + optional: true + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + + '@noble/curves@1.6.0': + dependencies: + '@noble/hashes': 1.5.0 + + '@noble/hashes@1.5.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@peculiar/asn1-schema@2.3.8': + dependencies: + asn1js: 3.0.5 + pvtsutils: 1.3.5 + tslib: 2.6.3 + + '@peculiar/json-schema@1.1.12': + dependencies: + tslib: 2.6.3 + + '@peculiar/webcrypto@1.4.6': + dependencies: + '@peculiar/asn1-schema': 2.3.8 + '@peculiar/json-schema': 1.1.12 + pvtsutils: 1.3.5 + tslib: 2.6.3 + webcrypto-core: 1.7.9 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.1.1': {} + + '@rollup/pluginutils@4.2.1': + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + + '@rushstack/eslint-patch@1.10.2': {} + + '@sinclair/typebox@0.25.24': {} + + '@tootallnate/once@2.0.0': {} + + '@ts-morph/common@0.11.1': + dependencies: + fast-glob: 3.3.2 + minimatch: 3.1.2 + mkdirp: 1.0.4 + path-browserify: 1.0.1 + + '@tsconfig/node10@1.0.9': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/body-parser@1.19.5': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.14.2 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.14.2 + + '@types/eslint@8.56.5': + dependencies: + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.5': {} + + '@types/express-serve-static-core@4.19.3': + dependencies: + '@types/node': 20.14.2 + '@types/qs': 6.9.15 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + + '@types/express@4.17.21': + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.19.3 + '@types/qs': 6.9.15 + '@types/serve-static': 1.15.7 + + '@types/http-errors@2.0.4': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/jsonwebtoken@9.0.6': + dependencies: + '@types/node': 20.14.2 + + '@types/mime@1.3.5': {} + + '@types/node@16.18.11': {} + + '@types/node@20.14.2': + dependencies: + undici-types: 5.26.5 + + '@types/normalize-package-data@2.4.4': {} + + '@types/prop-types@15.7.12': {} + + '@types/qs@6.9.15': {} + + '@types/range-parser@1.2.7': {} + + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.3 + + '@types/react@18.3.3': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + + '@types/semver@7.5.8': {} + + '@types/send@0.17.4': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.14.2 + + '@types/serve-static@1.15.7': + dependencies: + '@types/http-errors': 2.0.4 + '@types/node': 20.14.2 + '@types/send': 0.17.4 + + '@types/triple-beam@1.3.5': {} + + '@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@eslint-community/regexpp': 4.10.1 + '@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/scope-manager': 7.12.0 + '@typescript-eslint/type-utils': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/utils': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.12.0 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@typescript-eslint/scope-manager': 7.12.0 + '@typescript-eslint/types': 7.12.0 + '@typescript-eslint/typescript-estree': 7.12.0(typescript@5.4.5) + '@typescript-eslint/visitor-keys': 7.12.0 + debug: 4.3.5 + eslint: 8.57.0 + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/scope-manager@7.12.0': + dependencies: + '@typescript-eslint/types': 7.12.0 + '@typescript-eslint/visitor-keys': 7.12.0 + + '@typescript-eslint/type-utils@7.12.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@typescript-eslint/typescript-estree': 7.12.0(typescript@5.4.5) + '@typescript-eslint/utils': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + debug: 4.3.5 + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/types@7.12.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.4.5)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.6.2 + tsutils: 3.21.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@7.12.0(typescript@5.4.5)': + dependencies: + '@typescript-eslint/types': 7.12.0 + '@typescript-eslint/visitor-keys': 7.12.0 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.4 + semver: 7.6.2 + ts-api-utils: 1.3.0(typescript@5.4.5) + optionalDependencies: + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.4.5) + eslint: 8.57.0 + eslint-scope: 5.1.1 + semver: 7.6.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@7.12.0(eslint@8.57.0)(typescript@5.4.5)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 7.12.0 + '@typescript-eslint/types': 7.12.0 + '@typescript-eslint/typescript-estree': 7.12.0(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@7.12.0': + dependencies: + '@typescript-eslint/types': 7.12.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.2.0': {} + + '@vercel/build-utils@8.3.6': {} + + '@vercel/error-utils@2.0.2': {} + + '@vercel/fun@1.1.0': + dependencies: + '@tootallnate/once': 2.0.0 + async-listen: 1.2.0 + debug: 4.1.1 + execa: 3.2.0 + fs-extra: 8.1.0 + generic-pool: 3.4.2 + micro: 9.3.5-canary.3 + ms: 2.1.1 + node-fetch: 2.6.7 + path-match: 1.2.4 + promisepipe: 3.0.0 + semver: 7.3.5 + stat-mode: 0.3.0 + stream-to-promise: 2.2.0 + tar: 4.4.18 + tree-kill: 1.2.2 + uid-promise: 1.0.0 + uuid: 3.3.2 + xdg-app-paths: 5.1.0 + yauzl-promise: 2.1.3 + transitivePeerDependencies: + - encoding + - supports-color + + '@vercel/gatsby-plugin-vercel-analytics@1.0.11': + dependencies: + web-vitals: 0.2.4 + + '@vercel/gatsby-plugin-vercel-builder@2.0.40': + dependencies: + '@sinclair/typebox': 0.25.24 + '@vercel/build-utils': 8.3.6 + '@vercel/routing-utils': 3.1.0 + esbuild: 0.14.47 + etag: 1.8.1 + fs-extra: 11.1.0 + + '@vercel/go@3.1.1': {} + + '@vercel/hydrogen@1.0.4': + dependencies: + '@vercel/static-config': 3.0.0 + ts-morph: 12.0.0 + + '@vercel/next@4.3.6': + dependencies: + '@vercel/nft': 0.27.3 + transitivePeerDependencies: + - encoding + - supports-color + + '@vercel/nft@0.27.3': + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + '@rollup/pluginutils': 4.2.1 + acorn: 8.11.3 + acorn-import-attributes: 1.9.5(acorn@8.11.3) + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + micromatch: 4.0.7 + node-gyp-build: 4.8.1 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + '@vercel/node@3.2.8': + dependencies: + '@edge-runtime/node-utils': 2.3.0 + '@edge-runtime/primitives': 4.1.0 + '@edge-runtime/vm': 3.2.0 + '@types/node': 16.18.11 + '@vercel/build-utils': 8.3.6 + '@vercel/error-utils': 2.0.2 + '@vercel/nft': 0.27.3 + '@vercel/static-config': 3.0.0 + async-listen: 3.0.0 + cjs-module-lexer: 1.2.3 + edge-runtime: 2.5.9 + es-module-lexer: 1.4.1 + esbuild: 0.14.47 + etag: 1.8.1 + node-fetch: 2.6.9 + path-to-regexp: 6.2.1 + ts-morph: 12.0.0 + ts-node: 10.9.1(@types/node@16.18.11)(typescript@4.9.5) + typescript: 4.9.5 + undici: 5.28.4 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - supports-color + + '@vercel/python@4.3.1': {} + + '@vercel/redwood@2.1.3': + dependencies: + '@vercel/nft': 0.27.3 + '@vercel/routing-utils': 3.1.0 + '@vercel/static-config': 3.0.0 + semver: 6.3.1 + ts-morph: 12.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + '@vercel/remix-builder@2.2.6': + dependencies: + '@vercel/error-utils': 2.0.2 + '@vercel/nft': 0.27.3 + '@vercel/static-config': 3.0.0 + ts-morph: 12.0.0 + transitivePeerDependencies: + - encoding + - supports-color + + '@vercel/routing-utils@3.1.0': + dependencies: + path-to-regexp: 6.1.0 + optionalDependencies: + ajv: 6.12.6 + + '@vercel/ruby@2.1.0': {} + + '@vercel/static-build@2.5.18': + dependencies: + '@vercel/gatsby-plugin-vercel-analytics': 1.0.11 + '@vercel/gatsby-plugin-vercel-builder': 2.0.40 + '@vercel/static-config': 3.0.0 + ts-morph: 12.0.0 + + '@vercel/static-config@3.0.0': + dependencies: + ajv: 8.6.3 + json-schema-to-ts: 1.6.4 + ts-morph: 12.0.0 + + '@vercel/style-guide@6.0.0(@next/eslint-plugin-next@14.1.1)(eslint@8.57.0)(prettier@3.3.1)(typescript@5.4.5)': + dependencies: + '@babel/core': 7.24.7 + '@babel/eslint-parser': 7.24.1(@babel/core@7.24.7)(eslint@8.57.0) + '@rushstack/eslint-patch': 1.10.2 + '@typescript-eslint/eslint-plugin': 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + '@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + eslint-config-prettier: 9.1.0(eslint@8.57.0) + eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0) + eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) + eslint-plugin-playwright: 1.6.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0) + eslint-plugin-react: 7.34.1(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) + eslint-plugin-testing-library: 6.2.2(eslint@8.57.0)(typescript@5.4.5) + eslint-plugin-tsdoc: 0.2.17 + eslint-plugin-unicorn: 51.0.1(eslint@8.57.0) + eslint-plugin-vitest: 0.3.26(@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + prettier-plugin-packagejson: 2.5.0(prettier@3.3.1) + optionalDependencies: + '@next/eslint-plugin-next': 14.1.1 + eslint: 8.57.0 + prettier: 3.3.1 + typescript: 5.4.5 + transitivePeerDependencies: + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - jest + - supports-color + - vitest + + abbrev@1.1.1: {} + + acorn-import-attributes@1.9.5(acorn@8.11.3): + dependencies: + acorn: 8.11.3 + + acorn-jsx@5.3.2(acorn@8.11.3): + dependencies: + acorn: 8.11.3 + + acorn-walk@8.2.0: {} + + acorn@8.11.3: {} + + agent-base@6.0.2: + dependencies: + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.6.3: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: + optional: true + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: + optional: true + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + aproba@2.0.0: {} + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + arg@4.1.0: {} + + arg@4.1.3: {} + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array-includes@3.1.7: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + + array-union@2.1.0: {} + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-shim-unscopables: 1.0.2 + + array.prototype.findlastindex@1.2.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + get-intrinsic: 1.2.4 + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.toreversed@1.1.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-shim-unscopables: 1.0.2 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + + asap@2.0.6: {} + + asn1js@3.0.5: + dependencies: + pvtsutils: 1.3.5 + pvutils: 1.1.3 + tslib: 2.6.3 + + ast-types-flow@0.0.8: {} + + async-listen@1.2.0: {} + + async-listen@3.0.0: {} + + async-listen@3.0.1: {} + + async-sema@3.1.1: {} + + async@3.2.5: {} + + asynckit@0.4.0: {} + + auth0-deploy-cli@7.24.1: + dependencies: + ajv: 6.12.6 + auth0: 3.7.2 + dot-prop: 5.3.0 + fs-extra: 10.1.0 + global-agent: 2.2.0 + js-yaml: 4.1.0 + lodash: 4.17.21 + mkdirp: 1.0.4 + nconf: 0.12.1 + promise-pool-executor: 1.1.1 + sanitize-filename: 1.6.3 + winston: 3.14.2 + yargs: 15.4.1 + transitivePeerDependencies: + - debug + - superagent-proxy + - supports-color + + auth0@3.7.2: + dependencies: + axios: 1.7.4 + form-data: 3.0.1 + jsonwebtoken: 9.0.2 + jwks-rsa: 3.1.0 + lru-memoizer: 2.3.0 + rest-facade: 1.16.4 + retry: 0.13.1 + uuid: 9.0.1 + transitivePeerDependencies: + - debug + - superagent-proxy + - supports-color + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + axe-core@4.7.0: {} + + axios@1.7.4: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + axobject-query@3.2.1: + dependencies: + dequal: 2.0.3 + + balanced-match@1.0.2: {} + + base64-arraybuffer@0.2.0: {} + + base64-js@1.5.1: {} + + bignumber.js@9.1.2: {} + + binary-extensions@2.3.0: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + boolean@3.2.0: {} + + borc@2.1.2: + dependencies: + bignumber.js: 9.1.2 + buffer: 5.7.1 + commander: 2.20.3 + ieee754: 1.2.1 + iso-url: 0.4.7 + json-text-sequence: 0.1.1 + readable-stream: 3.6.2 + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.23.0: + dependencies: + caniuse-lite: 1.0.30001629 + electron-to-chromium: 1.4.791 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.0) + + buffer-crc32@0.2.13: {} + + buffer-equal-constant-time@1.0.1: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + builtin-modules@3.3.0: {} + + bytes@3.1.0: {} + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + + camel-case@1.2.2: + dependencies: + sentence-case: 1.1.3 + upper-case: 1.1.3 + + camelcase@5.3.1: {} + + caniuse-lite@1.0.30001629: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + change-case@2.3.1: + dependencies: + camel-case: 1.2.2 + constant-case: 1.1.2 + dot-case: 1.1.2 + is-lower-case: 1.1.3 + is-upper-case: 1.1.2 + lower-case: 1.1.4 + lower-case-first: 1.0.2 + param-case: 1.1.2 + pascal-case: 1.1.2 + path-case: 1.1.2 + sentence-case: 1.1.3 + snake-case: 1.1.2 + swap-case: 1.1.2 + title-case: 1.1.2 + upper-case: 1.1.3 + upper-case-first: 1.1.2 + + chokidar@3.3.1: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.3.0 + optionalDependencies: + fsevents: 2.1.3 + + chownr@1.1.4: {} + + chownr@2.0.0: {} + + ci-info@4.0.0: {} + + cjs-module-lexer@1.2.3: {} + + clean-regexp@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + code-block-writer@10.1.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color-support@1.1.3: {} + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: {} + + component-emitter@1.3.1: {} + + concat-map@0.0.1: {} + + console-control-strings@1.1.0: {} + + constant-case@1.1.2: + dependencies: + snake-case: 1.1.2 + upper-case: 1.1.3 + + content-type@1.0.4: {} + + convert-hrtime@3.0.0: {} + + convert-source-map@2.0.0: {} + + cookiejar@2.1.4: {} + + core-js-compat@3.37.1: + dependencies: + browserslist: 4.23.0 + + core-js@3.38.1: {} + + create-require@1.1.1: {} + + cross-spawn@7.0.3: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.1.3: {} + + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.1.1: + dependencies: + ms: 2.1.3 + + debug@4.3.5: + dependencies: + ms: 2.1.2 + + decamelize@1.2.0: {} + + deep-is@0.1.4: {} + + deepmerge@3.3.0: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + + delimit-stream@0.1.0: {} + + depd@1.1.2: {} + + dequal@2.0.3: {} + + detect-indent@7.0.1: {} + + detect-libc@2.0.3: {} + + detect-newline@4.0.1: {} + + detect-node@2.1.0: {} + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + + diff@4.0.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dot-case@1.1.2: + dependencies: + sentence-case: 1.1.3 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dotenv-cli@7.4.2: + dependencies: + cross-spawn: 7.0.3 + dotenv: 16.4.5 + dotenv-expand: 10.0.0 + minimist: 1.2.8 + + dotenv-expand@10.0.0: {} + + dotenv@16.0.3: {} + + dotenv@16.4.5: {} + + eastasianwidth@0.2.0: + optional: true + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + edge-runtime@2.5.9: + dependencies: + '@edge-runtime/format': 2.2.1 + '@edge-runtime/ponyfill': 2.4.2 + '@edge-runtime/vm': 3.2.0 + async-listen: 3.0.1 + mri: 1.2.0 + picocolors: 1.0.0 + pretty-ms: 7.0.1 + signal-exit: 4.0.2 + time-span: 4.0.0 + + electron-to-chromium@1.4.791: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enabled@2.0.0: {} + + end-of-stream@1.1.0: + dependencies: + once: 1.3.3 + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + enhanced-resolve@5.15.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.3 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.1 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.2 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + es-iterator-helpers@1.0.19: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + es-set-tostringtag: 2.0.3 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + globalthis: 1.0.3 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + iterator.prototype: 1.1.2 + safe-array-concat: 1.1.2 + + es-module-lexer@1.4.1: {} + + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + es6-error@4.1.1: {} + + esbuild-android-64@0.14.47: + optional: true + + esbuild-android-arm64@0.14.47: + optional: true + + esbuild-darwin-64@0.14.47: + optional: true + + esbuild-darwin-arm64@0.14.47: + optional: true + + esbuild-freebsd-64@0.14.47: + optional: true + + esbuild-freebsd-arm64@0.14.47: + optional: true + + esbuild-linux-32@0.14.47: + optional: true + + esbuild-linux-64@0.14.47: + optional: true + + esbuild-linux-arm64@0.14.47: + optional: true + + esbuild-linux-arm@0.14.47: + optional: true + + esbuild-linux-mips64le@0.14.47: + optional: true + + esbuild-linux-ppc64le@0.14.47: + optional: true + + esbuild-linux-riscv64@0.14.47: + optional: true + + esbuild-linux-s390x@0.14.47: + optional: true + + esbuild-netbsd-64@0.14.47: + optional: true + + esbuild-openbsd-64@0.14.47: + optional: true + + esbuild-sunos-64@0.14.47: + optional: true + + esbuild-windows-32@0.14.47: + optional: true + + esbuild-windows-64@0.14.47: + optional: true + + esbuild-windows-arm64@0.14.47: + optional: true + + esbuild@0.14.47: + optionalDependencies: + esbuild-android-64: 0.14.47 + esbuild-android-arm64: 0.14.47 + esbuild-darwin-64: 0.14.47 + esbuild-darwin-arm64: 0.14.47 + esbuild-freebsd-64: 0.14.47 + esbuild-freebsd-arm64: 0.14.47 + esbuild-linux-32: 0.14.47 + esbuild-linux-64: 0.14.47 + esbuild-linux-arm: 0.14.47 + esbuild-linux-arm64: 0.14.47 + esbuild-linux-mips64le: 0.14.47 + esbuild-linux-ppc64le: 0.14.47 + esbuild-linux-riscv64: 0.14.47 + esbuild-linux-s390x: 0.14.47 + esbuild-netbsd-64: 0.14.47 + esbuild-openbsd-64: 0.14.47 + esbuild-sunos-64: 0.14.47 + esbuild-windows-32: 0.14.47 + esbuild-windows-64: 0.14.47 + esbuild-windows-arm64: 0.14.47 + + escalade@3.1.2: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.0(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-config-turbo@2.0.1(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + eslint-plugin-turbo: 2.0.1(eslint@8.57.0) + + eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)): + dependencies: + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.13.1 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0): + dependencies: + debug: 4.3.5 + enhanced-resolve: 5.15.0 + eslint: 8.57.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + fast-glob: 3.3.2 + get-tsconfig: 4.7.2 + is-core-module: 2.13.1 + is-glob: 4.0.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-node + - eslint-import-resolver-webpack + - supports-color + + eslint-module-utils@2.8.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0) + transitivePeerDependencies: + - supports-color + + eslint-plugin-eslint-comments@3.2.0(eslint@8.57.0): + dependencies: + escape-string-regexp: 1.0.5 + eslint: 8.57.0 + ignore: 5.3.1 + + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + dependencies: + array-includes: 3.1.7 + array.prototype.findlastindex: 1.2.3 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + hasown: 2.0.2 + is-core-module: 2.13.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.7 + object.groupby: 1.0.1 + object.values: 1.1.7 + semver: 6.3.1 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5): + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-jsx-a11y@6.8.0(eslint@8.57.0): + dependencies: + '@babel/runtime': 7.24.7 + aria-query: 5.3.0 + array-includes: 3.1.7 + array.prototype.flatmap: 1.3.2 + ast-types-flow: 0.0.8 + axe-core: 4.7.0 + axobject-query: 3.2.1 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + es-iterator-helpers: 1.0.19 + eslint: 8.57.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.entries: 1.1.7 + object.fromentries: 2.0.7 + + eslint-plugin-only-warn@1.1.0: {} + + eslint-plugin-playwright@1.6.0(eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + globals: 13.24.0 + optionalDependencies: + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + + eslint-plugin-react-hooks@4.6.2(eslint@8.57.0): + dependencies: + eslint: 8.57.0 + + eslint-plugin-react@7.34.1(eslint@8.57.0): + dependencies: + array-includes: 3.1.7 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.2 + array.prototype.toreversed: 1.1.2 + array.prototype.tosorted: 1.1.3 + doctrine: 2.1.0 + es-iterator-helpers: 1.0.19 + eslint: 8.57.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.7 + object.fromentries: 2.0.7 + object.hasown: 1.1.3 + object.values: 1.1.7 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.10 + + eslint-plugin-testing-library@6.2.2(eslint@8.57.0)(typescript@5.4.5): + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-tsdoc@0.2.17: + dependencies: + '@microsoft/tsdoc': 0.14.2 + '@microsoft/tsdoc-config': 0.16.2 + + eslint-plugin-turbo@2.0.1(eslint@8.57.0): + dependencies: + dotenv: 16.0.3 + eslint: 8.57.0 + + eslint-plugin-unicorn@51.0.1(eslint@8.57.0): + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint/eslintrc': 2.1.4 + ci-info: 4.0.0 + clean-regexp: 1.0.0 + core-js-compat: 3.37.1 + eslint: 8.57.0 + esquery: 1.5.0 + indent-string: 4.0.0 + is-builtin-module: 3.2.1 + jsesc: 3.0.2 + pluralize: 8.0.0 + read-pkg-up: 7.0.1 + regexp-tree: 0.1.27 + regjsparser: 0.10.0 + semver: 7.6.2 + strip-indent: 3.0.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5): + dependencies: + '@typescript-eslint/utils': 7.12.0(eslint@8.57.0)(typescript@5.4.5) + eslint: 8.57.0 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 7.12.0(@typescript-eslint/parser@7.12.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.0: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.10.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.5 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 3.4.3 + + esquery@1.5.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + events-intercept@2.0.0: {} + + execa@3.2.0: + dependencies: + cross-spawn: 7.0.3 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + p-finally: 2.0.1 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.7 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fecha@4.2.3: {} + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + file-uri-to-path@1.0.0: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.1: {} + + fn.name@1.1.0: {} + + follow-redirects@1.15.6: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.1.1: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + optional: true + + form-data@3.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + formidable@2.1.2: + dependencies: + dezalgo: 1.0.4 + hexoid: 1.0.0 + once: 1.4.0 + qs: 6.12.1 + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@11.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-minipass@1.2.7: + dependencies: + minipass: 2.9.0 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + fsevents@2.1.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gauge@3.0.2: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + generic-pool@3.4.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-stdin@9.0.0: {} + + get-stream@5.2.0: + dependencies: + pump: 3.0.0 + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + get-tsconfig@4.7.2: + dependencies: + resolve-pkg-maps: 1.0.0 + + git-hooks-list@3.1.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.3.10: + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.4 + minipass: 7.1.2 + path-scurry: 1.11.1 + optional: true + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-agent@2.2.0: + dependencies: + boolean: 3.2.0 + core-js: 3.38.1 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.6.2 + serialize-error: 7.0.1 + + globals@11.12.0: {} + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.3: + dependencies: + define-properties: 1.2.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 + + globby@13.2.2: + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 4.0.0 + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-bigints@1.0.2: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + has-unicode@2.0.1: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hexoid@1.0.0: {} + + hosted-git-info@2.8.9: {} + + http-errors@1.4.0: + dependencies: + inherits: 2.0.1 + statuses: 1.5.0 + + http-errors@1.7.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.1.1 + statuses: 1.5.0 + toidentifier: 1.0.0 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.3.5 + transitivePeerDependencies: + - supports-color + + human-signals@1.1.1: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.1: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.1: {} + + inherits@2.0.4: {} + + ini@2.0.0: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.0.6 + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: {} + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-builtin-module@3.2.1: + dependencies: + builtin-modules: 3.3.0 + + is-callable@1.2.7: {} + + is-core-module@2.13.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.0.10: + dependencies: + has-tostringtag: 1.0.2 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-lower-case@1.1.3: + dependencies: + lower-case: 1.1.4 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@4.1.0: {} + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-stream@2.0.1: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-upper-case@1.1.2: + dependencies: + upper-case: 1.1.3 + + is-weakmap@2.0.2: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + is-weakset@2.0.3: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + isarray@0.0.1: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iso-url@0.4.7: {} + + iterator.prototype@1.1.2: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + reflect.getprototypeof: 1.0.4 + set-function-name: 2.0.1 + + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + optional: true + + jju@1.4.0: {} + + jose@4.15.5: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@0.5.0: {} + + jsesc@2.5.2: {} + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-to-ts@1.6.4: + dependencies: + '@types/json-schema': 7.0.15 + ts-toolbelt: 6.15.5 + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json-stringify-safe@5.0.1: {} + + json-text-sequence@0.1.1: + dependencies: + delimit-stream: 0.1.0 + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.2 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.7 + array.prototype.flat: 1.3.2 + object.assign: 4.1.5 + object.values: 1.1.7 + + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jwks-rsa@3.1.0: + dependencies: + '@types/express': 4.17.21 + '@types/jsonwebtoken': 9.0.6 + debug: 4.3.5 + jose: 4.15.5 + limiter: 1.1.5 + lru-memoizer: 2.3.0 + transitivePeerDependencies: + - supports-color + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kuler@2.0.0: {} + + language-subtag-registry@0.3.22: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.22 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + limiter@1.1.5: {} + + lines-and-columns@1.2.4: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.clonedeep@4.5.0: {} + + lodash.get@4.4.2: {} + + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + lodash@4.17.21: {} + + logform@2.6.1: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.4.3 + triple-beam: 1.4.1 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lower-case-first@1.0.2: + dependencies: + lower-case: 1.1.4 + + lower-case@1.1.4: {} + + lru-cache@10.2.2: + optional: true + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-memoizer@2.3.0: + dependencies: + lodash.clonedeep: 4.5.0 + lru-cache: 6.0.0 + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + + make-error@1.3.6: {} + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micro@9.3.5-canary.3: + dependencies: + arg: 4.1.0 + content-type: 1.0.4 + raw-body: 2.4.1 + + micromatch@4.0.7: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.4: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@2.9.0: + dependencies: + safe-buffer: 5.2.1 + yallist: 3.1.1 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: + optional: true + + minizlib@1.3.3: + dependencies: + minipass: 2.9.0 + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + mri@1.2.0: {} + + ms@2.1.1: {} + + ms@2.1.2: {} + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + nconf@0.12.1: + dependencies: + async: 3.2.5 + ini: 2.0.0 + secure-keys: 1.0.0 + yargs: 16.2.0 + + next-tick@1.1.0: {} + + node-fetch@2.6.7: + dependencies: + whatwg-url: 5.0.0 + + node-fetch@2.6.9: + dependencies: + whatwg-url: 5.0.0 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build@4.8.1: {} + + node-releases@2.0.14: {} + + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + + normalize-package-data@2.5.0: + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.8 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.1: {} + + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.entries@1.1.7: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.fromentries@2.0.7: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.groupby@1.0.1: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + get-intrinsic: 1.2.4 + + object.hasown@1.1.3: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.23.3 + + object.values@1.1.7: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + + once@1.3.3: + dependencies: + wrappy: 1.0.2 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + os-paths@4.4.0: {} + + p-defer@1.0.0: {} + + p-defer@3.0.0: {} + + p-finally@2.0.1: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + param-case@1.1.2: + dependencies: + sentence-case: 1.1.3 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.24.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-ms@2.1.0: {} + + pascal-case@1.1.2: + dependencies: + camel-case: 1.2.2 + upper-case-first: 1.1.2 + + path-browserify@1.0.1: {} + + path-case@1.1.2: + dependencies: + sentence-case: 1.1.3 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-match@1.2.4: + dependencies: + http-errors: 1.4.0 + path-to-regexp: 1.8.0 + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.2.2 + minipass: 7.1.2 + optional: true + + path-to-regexp@1.8.0: + dependencies: + isarray: 0.0.1 + + path-to-regexp@6.1.0: {} + + path-to-regexp@6.2.1: {} + + path-type@4.0.0: {} + + pend@1.2.0: {} + + picocolors@1.0.0: {} + + picocolors@1.0.1: {} + + picomatch@2.3.1: {} + + pluralize@8.0.0: {} + + possible-typed-array-names@1.0.0: {} + + prelude-ls@1.2.1: {} + + prettier-plugin-packagejson@2.5.0(prettier@3.3.1): + dependencies: + sort-package-json: 2.10.0 + synckit: 0.9.0 + optionalDependencies: + prettier: 3.3.1 + + prettier@3.3.1: {} + + pretty-ms@7.0.1: + dependencies: + parse-ms: 2.1.0 + + promise-batcher@1.1.1: + dependencies: + p-defer: 3.0.0 + + promise-pool-executor@1.1.1: + dependencies: + debug: 3.2.7 + next-tick: 1.1.0 + p-defer: 1.0.0 + promise-batcher: 1.1.1 + transitivePeerDependencies: + - supports-color + + promisepipe@3.0.0: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@1.1.0: {} + + pump@3.0.0: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@2.3.0: {} + + pvtsutils@1.3.5: + dependencies: + tslib: 2.6.3 + + pvutils@1.1.3: {} + + qs@6.12.1: + dependencies: + side-channel: 1.0.6 + + queue-microtask@1.2.3: {} + + raw-body@2.4.1: + dependencies: + bytes: 3.1.0 + http-errors: 1.7.3 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-is@16.13.1: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-pkg-up@7.0.1: + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + + read-pkg@5.2.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.3.0: + dependencies: + picomatch: 2.3.1 + + reflect.getprototypeof@1.0.4: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + get-intrinsic: 1.2.4 + globalthis: 1.0.3 + which-builtin-type: 1.1.3 + + regenerator-runtime@0.14.1: {} + + regexp-tree@0.1.27: {} + + regexp.prototype.flags@1.5.2: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.1 + + regjsparser@0.10.0: + dependencies: + jsesc: 0.5.0 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-main-filename@2.0.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.19.0: + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + + resolve@1.22.8: + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.13.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rest-facade@1.16.4: + dependencies: + change-case: 2.3.1 + deepmerge: 3.3.0 + lodash.get: 4.4.2 + superagent: 7.1.6 + transitivePeerDependencies: + - supports-color + + retry@0.13.1: {} + + reusify@1.0.4: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.3 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + safe-stable-stringify@2.4.3: {} + + safer-buffer@2.1.2: {} + + sanitize-filename@1.6.3: + dependencies: + truncate-utf8-bytes: 1.0.2 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + secure-keys@1.0.0: {} + + semver-compare@1.0.0: {} + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.3.5: + dependencies: + lru-cache: 6.0.0 + + semver@7.6.2: {} + + sentence-case@1.1.3: + dependencies: + lower-case: 1.1.4 + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.1: + dependencies: + define-data-property: 1.1.4 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + setprototypeof@1.1.1: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.1 + + signal-exit@3.0.7: {} + + signal-exit@4.0.2: {} + + signal-exit@4.1.0: + optional: true + + simple-cbor@0.4.1: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + slash@3.0.0: {} + + slash@4.0.0: {} + + snake-case@1.1.2: + dependencies: + sentence-case: 1.1.3 + + sort-object-keys@1.1.3: {} + + sort-package-json@2.10.0: + dependencies: + detect-indent: 7.0.1 + detect-newline: 4.0.1 + get-stdin: 9.0.0 + git-hooks-list: 3.1.0 + globby: 13.2.2 + is-plain-obj: 4.1.0 + semver: 7.6.2 + sort-object-keys: 1.1.3 + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.16 + + spdx-exceptions@2.3.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.16 + + spdx-license-ids@3.0.16: {} + + sprintf-js@1.1.3: {} + + stack-trace@0.0.10: {} + + stat-mode@0.3.0: {} + + statuses@1.5.0: {} + + stream-to-array@2.3.0: + dependencies: + any-promise: 1.3.0 + + stream-to-promise@2.2.0: + dependencies: + any-promise: 1.3.0 + end-of-stream: 1.1.0 + stream-to-array: 2.3.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + optional: true + + string.prototype.matchall@4.0.10: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + internal-slot: 1.0.7 + regexp.prototype.flags: 1.5.2 + set-function-name: 2.0.1 + side-channel: 1.0.6 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + optional: true + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + superagent@7.1.6: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.5 + fast-safe-stringify: 2.1.1 + form-data: 4.0.0 + formidable: 2.1.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.12.1 + readable-stream: 3.6.2 + semver: 7.6.2 + transitivePeerDependencies: + - supports-color + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swap-case@1.1.2: + dependencies: + lower-case: 1.1.4 + upper-case: 1.1.3 + + synckit@0.9.0: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.6.3 + + tapable@2.2.1: {} + + tar@4.4.18: + dependencies: + chownr: 1.1.4 + fs-minipass: 1.2.7 + minipass: 2.9.0 + minizlib: 1.3.3 + mkdirp: 0.5.6 + safe-buffer: 5.2.1 + yallist: 3.1.1 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + text-hex@1.0.0: {} + + text-table@0.2.0: {} + + time-span@4.0.0: + dependencies: + convert-hrtime: 3.0.0 + + title-case@1.1.2: + dependencies: + sentence-case: 1.1.3 + upper-case: 1.1.3 + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.0: {} + + tr46@0.0.3: {} + + tree-kill@1.2.2: {} + + triple-beam@1.4.1: {} + + truncate-utf8-bytes@1.0.2: + dependencies: + utf8-byte-length: 1.0.5 + + ts-api-utils@1.3.0(typescript@5.4.5): + dependencies: + typescript: 5.4.5 + + ts-morph@12.0.0: + dependencies: + '@ts-morph/common': 0.11.1 + code-block-writer: 10.1.1 + + ts-node@10.9.1(@types/node@16.18.11)(typescript@4.9.5): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 16.18.11 + acorn: 8.11.3 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.9.5 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + ts-toolbelt@6.15.5: {} + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@1.14.1: {} + + tslib@2.6.3: {} + + tsutils@3.21.0(typescript@5.4.5): + dependencies: + tslib: 1.14.1 + typescript: 5.4.5 + + turbo-darwin-64@2.0.9: + optional: true + + turbo-darwin-arm64@2.0.9: + optional: true + + turbo-linux-64@2.0.9: + optional: true + + turbo-linux-arm64@2.0.9: + optional: true + + turbo-windows-64@2.0.9: + optional: true + + turbo-windows-arm64@2.0.9: + optional: true + + turbo@2.0.9: + optionalDependencies: + turbo-darwin-64: 2.0.9 + turbo-darwin-arm64: 2.0.9 + turbo-linux-64: 2.0.9 + turbo-linux-arm64: 2.0.9 + turbo-windows-64: 2.0.9 + turbo-windows-arm64: 2.0.9 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.13.1: {} + + type-fest@0.20.2: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typescript@4.9.5: {} + + typescript@5.4.5: {} + + uid-promise@1.0.0: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + undici-types@5.26.5: {} + + undici@5.28.4: + dependencies: + '@fastify/busboy': 2.1.1 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.0.16(browserslist@4.23.0): + dependencies: + browserslist: 4.23.0 + escalade: 3.1.2 + picocolors: 1.0.1 + + upper-case-first@1.1.2: + dependencies: + upper-case: 1.1.3 + + upper-case@1.1.3: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.0 + + utf8-byte-length@1.0.5: {} + + util-deprecate@1.0.2: {} + + uuid@3.3.2: {} + + uuid@9.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + vercel@37.1.0: + dependencies: + '@vercel/build-utils': 8.3.6 + '@vercel/fun': 1.1.0 + '@vercel/go': 3.1.1 + '@vercel/hydrogen': 1.0.4 + '@vercel/next': 4.3.6 + '@vercel/node': 3.2.8 + '@vercel/python': 4.3.1 + '@vercel/redwood': 2.1.3 + '@vercel/remix-builder': 2.2.6 + '@vercel/ruby': 2.1.0 + '@vercel/static-build': 2.5.18 + chokidar: 3.3.1 + transitivePeerDependencies: + - '@swc/core' + - '@swc/wasm' + - encoding + - supports-color + + web-vitals@0.2.4: {} + + webcrypto-core@1.7.9: + dependencies: + '@peculiar/asn1-schema': 2.3.8 + '@peculiar/json-schema': 1.1.12 + asn1js: 3.0.5 + pvtsutils: 1.3.5 + tslib: 2.6.3 + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-builtin-type@1.1.3: + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.0.10 + is-regex: 1.1.4 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.2 + which-typed-array: 1.1.15 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.3 + + which-module@2.0.1: {} + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + winston-transport@4.7.1: + dependencies: + logform: 2.6.1 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.14.2: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.5 + is-stream: 2.0.1 + logform: 2.6.1 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.7.1 + + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + optional: true + + wrappy@1.0.2: {} + + xdg-app-paths@5.1.0: + dependencies: + xdg-portable: 7.3.0 + + xdg-portable@7.3.0: + dependencies: + os-paths: 4.4.0 + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@20.2.9: {} + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yauzl-clone@1.0.4: + dependencies: + events-intercept: 2.0.0 + + yauzl-promise@2.1.3: + dependencies: + yauzl: 2.10.0 + yauzl-clone: 1.0.4 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..3ff5faa --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "apps/*" + - "packages/*" diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..a2e9d55 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "1.77.0" +components = ["rustfmt", "clippy"] +targets = ["wasm32-unknown-unknown"] +profile = "minimal" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c8a018d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@repo/typescript-config/base.json" +} diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..8bc7357 --- /dev/null +++ b/turbo.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://turbo.build/schema.json", + "globalDependencies": ["**/.env.*local"], + "tasks": { + "build": { + "dependsOn": ["^build"] + }, + "deploy:local": { + "dependsOn": ["build", "lint"] + }, + "deploy": { + "dependsOn": ["^build"], + "env": [ + "AUTH0_DOMAIN", + "AUTH0_CLIENT_ID", + "AUTH0_CLIENT_SECRET", + "AUTH0_PRESERVE_KEYWORDS", + "AUTH0_ALLOW_DELETE", + "WEB_APP_URL", + "HASURA_GRAPHQL_ENDPOINT", + "HASURA_GRAPHQL_ADMIN_SECRET" + ] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "pretest": { + "dependsOn": ["^pretest"] + }, + "test": { + "dependsOn": ["pretest"] + } + } +}