diff --git a/.fpm b/.fpm index b982fd8328..546ce641cf 100644 --- a/.fpm +++ b/.fpm @@ -1,6 +1,5 @@ -s dir --name defguard ---architecture x86_64 --description "defguard core service" --url "https://defguard.net/" --maintainer "teonite" diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 0000000000..1af7b844e6 --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,86 @@ +name: Build Docker image + +on: + workflow_call: + inputs: + tags: + description: "List of tags as key-value pair attributes" + required: false + type: string + +env: + GHCR_REPO: ghcr.io/defguard/defguard + +jobs: + build-docker: + runs-on: + - self-hosted + - Linux + - ${{ matrix.runner }} + strategy: + matrix: + cpu: [arm64, amd64, arm/v7] + include: + - cpu: arm64 + runner: ARM64 + tag: arm64 + - cpu: amd64 + runner: X64 + tag: amd64 + - cpu: arm/v7 + runner: ARM + tag: armv7 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Login to GitHub container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + buildkitd-config-inline: | + [registry."docker.io"] + mirrors = ["dockerhub-proxy.teonite.net"] + - name: Build container + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/${{ matrix.cpu }} + provenance: false + push: true + tags: "${{ env.GHCR_REPO }}:${{ github.sha }}-${{ matrix.tag }}" + cache-from: type=gha + cache-to: type=gha,mode=max + + docker-manifest: + runs-on: [self-hosted, Linux] + needs: [build-docker] + steps: + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.GHCR_REPO }} + tags: ${{ inputs.tags }} + - name: Login to GitHub container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Create and push manifests + run: | + tags='${{ env.GHCR_REPO }}:${{ github.sha }} ${{ steps.meta.outputs.tags }}' + for tag in ${tags} + do + docker manifest rm ${tag} || true + docker manifest create ${tag} ${{ env.GHCR_REPO }}:${{ github.sha }}-amd64 ${{ env.GHCR_REPO }}:${{ github.sha }}-arm64 ${{ env.GHCR_REPO }}:${{ github.sha }}-armv7 + docker manifest push ${tag} + done diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4bb06a295b..eece717f6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,12 +21,12 @@ env: jobs: test: - runs-on: [self-hosted, Linux] - container: rust:1.77 + runs-on: [self-hosted, Linux, X64] + container: rust:1 services: postgres: - image: postgres:14-alpine + image: postgres:15-alpine env: POSTGRES_DB: defguard POSTGRES_USER: defguard diff --git a/.github/workflows/current.yml b/.github/workflows/current.yml index b2c6aabc92..1f64350206 100644 --- a/.github/workflows/current.yml +++ b/.github/workflows/current.yml @@ -5,50 +5,23 @@ on: - main - dev paths-ignore: - - '*.md' - - 'LICENSE' + - "*.md" + - "LICENSE" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: - build-docker: - runs-on: [self-hosted, Linux] - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ghcr.io/defguard/defguard - tags: | - type=raw,value=current - type=ref,event=branch - type=sha - - name: Login to GitHub container registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - buildkitd-config-inline: | - [registry."docker.io"] - mirrors = ["dockerhub-proxy.teonite.net"] - - name: Build container - uses: docker/build-push-action@v5 - with: - context: . - platforms: linux/amd64 - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + build-current: + uses: ./.github/workflows/build-docker.yml + with: + tags: | + type=raw,value=current + type=ref,event=branch + type=sha + trigger-e2e: - needs: build-docker + needs: build-current uses: ./.github/workflows/e2e.yml secrets: inherit diff --git a/.github/workflows/dev-deployment.yml b/.github/workflows/dev-deployment.yml index da8cc336a6..3e0f72cc16 100644 --- a/.github/workflows/dev-deployment.yml +++ b/.github/workflows/dev-deployment.yml @@ -4,7 +4,7 @@ on: jobs: deploy-dev: - runs-on: [self-hosted, Linux] + runs-on: [self-hosted, Linux, X64] environment: DEV if: ${{ github.event_name != 'pull_request' && github.ref_name == 'dev' }} env: @@ -15,6 +15,6 @@ jobs: - name: Add SHORT_SHA env variable run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-7`" >> $GITHUB_ENV - name: Deploy new image version - uses: actions-hub/kubectl@v1.30.0 + uses: actions-hub/kubectl@v1.30.3 with: args: --namespace defguard-dev set image deployment/defguard defguard=ghcr.io/defguard/defguard:sha-${{ env.SHORT_SHA }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 3ab0a3ba40..5c968f41d4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,45 +15,24 @@ env: jobs: rustdoc: - runs-on: [self-hosted, Linux] - container: rust:1.77 - services: - postgres: - image: postgres:15-alpine - env: - POSTGRES_DB: defguard - POSTGRES_USER: defguard - POSTGRES_PASSWORD: defguard - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - + runs-on: [self-hosted, Linux, X64] + container: rust:1-slim steps: - name: Checkout uses: actions/checkout@v4 with: submodules: recursive - - name: Set database URL - run: sed -i -e 's,localhost,postgres,' .env - - name: Install protoc run: apt-get update && apt-get -y install protobuf-compiler - name: Build Docs env: - DEFGUARD_DB_HOST: postgres - DEFGUARD_DB_PORT: 5432 - DEFGUARD_DB_NAME: defguard - DEFGUARD_DB_USER: defguard - DEFGUARD_DB_PASSWORD: defguard SQLX_OFFLINE: true - run: cargo doc --all --no-deps + run: cargo doc --no-deps --workspace - name: Deploy Docs - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_branch: gh-pages diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index bc3f1180ba..3c029171d0 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -5,7 +5,7 @@ on: jobs: test: - runs-on: [self-hosted, Linux] + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx diff --git a/.github/workflows/lint-e2e.yml b/.github/workflows/lint-e2e.yml index f3cadcbed3..0ad127e093 100644 --- a/.github/workflows/lint-e2e.yml +++ b/.github/workflows/lint-e2e.yml @@ -2,22 +2,24 @@ on: push: branches: - main + - dev paths: - "e2e/**" pull_request: branches: - main + - dev paths: - "e2e/**" jobs: lint-e2e: - runs-on: [self-hosted, Linux] + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 19 + node-version: 20 - name: install deps working-directory: ./e2e run: | diff --git a/.github/workflows/lint-web.yml b/.github/workflows/lint-web.yml index 98ab096400..487825f268 100644 --- a/.github/workflows/lint-web.yml +++ b/.github/workflows/lint-web.yml @@ -2,24 +2,26 @@ on: push: branches: - main + - dev paths: - "web/**" pull_request: branches: - main + - dev paths: - "web/**" jobs: lint-web: - runs-on: [self-hosted, Linux] + runs-on: [self-hosted, Linux, X64] steps: - uses: actions/checkout@v4 with: - submodules: 'recursive' + submodules: "recursive" - uses: actions/setup-node@v4 with: - node-version: 19 + node-version: 20 - name: install deps working-directory: ./web run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eabc89476b..791d166ba5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,50 +4,19 @@ on: tags: - v*.*.* +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - publish-docker: - runs-on: [self-hosted, Linux] - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ghcr.io/DefGuard/defguard - tags: | - type=raw,value=latest - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker BuildX - uses: docker/setup-buildx-action@v3 - with: - buildkitd-config-inline: | - [registry."docker.io"] - mirrors = ["dockerhub-proxy.teonite.net"] - - name: Login to GitHub container registry - if: github.event_name != 'pull_request' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build container - uses: docker/build-push-action@v5 - with: - context: . - platforms: linux/amd64, linux/arm64 - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + build-latest: + uses: ./.github/workflows/build-docker.yml + with: + tags: | + type=raw,value=latest + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha create-release: name: create-release @@ -57,32 +26,32 @@ jobs: steps: - name: Create GitHub release id: release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: startsWith(github.ref, 'refs/tags/') with: draft: true generate_release_notes: true build-binaries: - needs: [ "create-release" ] + needs: [create-release] runs-on: - self-hosted - ${{ matrix.os }} strategy: fail-fast: false matrix: - build: [ linux, linux-arm, linux-arm64, freebsd ] + build: [linux, linux-arm64, freebsd] include: - build: linux + arch: amd64 os: Linux target: x86_64-unknown-linux-gnu - - build: linux-arm - os: Linux - target: armv7-unknown-linux-gnueabihf - build: linux-arm64 + arch: arm64 os: Linux target: aarch64-unknown-linux-gnu - build: freebsd + arch: amd64 os: Linux target: x86_64-unknown-freebsd steps: @@ -121,7 +90,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - cache: 'pnpm' + cache: "pnpm" cache-dependency-path: ./web/pnpm-lock.yaml - name: Install frontend dependencies @@ -162,10 +131,10 @@ jobs: - name: Build DEB package if: matrix.build == 'linux' - uses: bpicode/github-action-fpm@master + uses: defGuard/fpm-action@main with: fpm_args: "defguard-${{ github.ref_name }}-${{ matrix.target }}=/usr/bin/defguard defguard.service=/usr/lib/systemd/system/defguard.service .env=/etc/defguard/core.conf" - fpm_opts: "--debug --output-type deb --version ${{ env.VERSION }} --package defguard-${{ env.VERSION }}-${{ matrix.target }}.deb" + fpm_opts: "--architecture ${{ matrix.arch }} --debug --output-type deb --version ${{ env.VERSION }} --package defguard-${{ env.VERSION }}-${{ matrix.target }}.deb" - name: Upload DEB if: matrix.build == 'linux' @@ -180,10 +149,10 @@ jobs: - name: Build RPM package if: matrix.build == 'linux' - uses: bpicode/github-action-fpm@master + uses: defGuard/fpm-action@main with: fpm_args: "defguard-${{ github.ref_name }}-${{ matrix.target }}=/usr/bin/defguard defguard.service=/usr/lib/systemd/system/defguard.service .env=/etc/defguard/core.conf" - fpm_opts: "--debug --output-type rpm --version ${{ env.VERSION }} --package defguard-${{ env.VERSION }}-${{ matrix.target }}.rpm" + fpm_opts: "--architecture ${{ matrix.arch }} --debug --output-type rpm --version ${{ env.VERSION }} --package defguard-${{ env.VERSION }}-${{ matrix.target }}.rpm" - name: Upload RPM if: matrix.build == 'linux' @@ -195,3 +164,21 @@ jobs: asset_path: defguard-${{ env.VERSION }}-${{ matrix.target }}.rpm asset_name: defguard-${{ env.VERSION }}-${{ matrix.target }}.rpm asset_content_type: application/octet-stream + + - name: Build FreeBSD package + if: matrix.build == 'freebsd' + uses: defGuard/fpm-action@main + with: + fpm_args: "${{ matrix.asset_name }}-${{ github.ref_name }}=/usr/local/bin/defguard defguard.service.freebsd=/usr/local/etc/rc.d/defguard" + fpm_opts: "--architecture ${{ matrix.arch }} --debug --output-type freebsd --version ${{ env.VERSION }} --package defguard-${{ env.VERSION }}_${{ matrix.target }}.pkg --freebsd-osversion '*'" + + - name: Upload FreeBSD + if: matrix.build == 'freebsd' + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: defguard-${{ env.VERSION }}_${{ matrix.target }}.pkg + asset_name: defguard-${{ env.VERSION }}_${{ matrix.target }}.pkg + asset_content_type: application/octet-stream diff --git a/.sqlx/query-336532632eb24ed923842cdc8976e26a7f9b8d28ef1d7011407f388d3e983e62.json b/.sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json similarity index 80% rename from .sqlx/query-336532632eb24ed923842cdc8976e26a7f9b8d28ef1d7011407f388d3e983e62.json rename to .sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json index ca8985f9ce..4002b02764 100644 --- a/.sqlx/query-336532632eb24ed923842cdc8976e26a7f9b8d28ef1d7011407f388d3e983e62.json +++ b/.sqlx/query-054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\"", + "query": "SELECT id, \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -70,5 +70,5 @@ false ] }, - "hash": "336532632eb24ed923842cdc8976e26a7f9b8d28ef1d7011407f388d3e983e62" + "hash": "054e9d1f22c77a3b793616e92ab97e16fc9d6814e41f8d30e3e2a8431181c347" } diff --git a/.sqlx/query-05c83ab93f896e0555eabddab41b2440e7d6804e1f391155a4388060851d2ccf.json b/.sqlx/query-05c83ab93f896e0555eabddab41b2440e7d6804e1f391155a4388060851d2ccf.json new file mode 100644 index 0000000000..a468ee3ab4 --- /dev/null +++ b/.sqlx/query-05c83ab93f896e0555eabddab41b2440e7d6804e1f391155a4388060851d2ccf.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, \"token\",\"device_id\",\"created_at\" FROM \"pollingtoken\" WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "token", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "device_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "05c83ab93f896e0555eabddab41b2440e7d6804e1f391155a4388060851d2ccf" +} diff --git a/.sqlx/query-6be6b6c94a21c0352c9653f2f5606187cfee43d39cf7e5ea9c2f53a9a4666f77.json b/.sqlx/query-0b6daf46f6493f08f28a50a0f79de7ed60d46d90c9f226418a0225fa34ccfdcf.json similarity index 79% rename from .sqlx/query-6be6b6c94a21c0352c9653f2f5606187cfee43d39cf7e5ea9c2f53a9a4666f77.json rename to .sqlx/query-0b6daf46f6493f08f28a50a0f79de7ed60d46d90c9f226418a0225fa34ccfdcf.json index 0cd0953fcf..6d9dd22dae 100644 --- a/.sqlx/query-6be6b6c94a21c0352c9653f2f5606187cfee43d39cf7e5ea9c2f53a9a4666f77.json +++ b/.sqlx/query-0b6daf46f6493f08f28a50a0f79de7ed60d46d90c9f226418a0225fa34ccfdcf.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"yubikey_id\",\"name\",\"user_id\",\"key\",\"key_type\" \"key_type: _\" FROM \"authentication_key\" WHERE id = $1", + "query": "SELECT id, \"yubikey_id\",\"name\",\"user_id\",\"key\",\"key_type\" \"key_type: _\" FROM \"authentication_key\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -58,5 +58,5 @@ false ] }, - "hash": "6be6b6c94a21c0352c9653f2f5606187cfee43d39cf7e5ea9c2f53a9a4666f77" + "hash": "0b6daf46f6493f08f28a50a0f79de7ed60d46d90c9f226418a0225fa34ccfdcf" } diff --git a/.sqlx/query-06be7b39c87c3a1d1465dfb007937b5b584f60e9094a18936b482c4dfb6b1aca.json b/.sqlx/query-0d04ddf1bf7cf709235a62b56ea6415eaf4f0e5c36dc95ee6b55e451f4715997.json similarity index 67% rename from .sqlx/query-06be7b39c87c3a1d1465dfb007937b5b584f60e9094a18936b482c4dfb6b1aca.json rename to .sqlx/query-0d04ddf1bf7cf709235a62b56ea6415eaf4f0e5c36dc95ee6b55e451f4715997.json index d70e49da1c..8662cc6357 100644 --- a/.sqlx/query-06be7b39c87c3a1d1465dfb007937b5b584f60e9094a18936b482c4dfb6b1aca.json +++ b/.sqlx/query-0d04ddf1bf7cf709235a62b56ea6415eaf4f0e5c36dc95ee6b55e451f4715997.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\" FROM \"group\"", + "query": "SELECT id, \"name\" FROM \"group\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -22,5 +22,5 @@ false ] }, - "hash": "06be7b39c87c3a1d1465dfb007937b5b584f60e9094a18936b482c4dfb6b1aca" + "hash": "0d04ddf1bf7cf709235a62b56ea6415eaf4f0e5c36dc95ee6b55e451f4715997" } diff --git a/.sqlx/query-1a45960695ad8a4f60cd94b610f9cf0c9b09ea742ea07a8de4ef8e6b199f2269.json b/.sqlx/query-0e9a8c2395d43898748c176db0d89fa20784cd93ca1dfd00b2755934937d8270.json similarity index 62% rename from .sqlx/query-1a45960695ad8a4f60cd94b610f9cf0c9b09ea742ea07a8de4ef8e6b199f2269.json rename to .sqlx/query-0e9a8c2395d43898748c176db0d89fa20784cd93ca1dfd00b2755934937d8270.json index 6b707bc7bd..25c3f61e04 100644 --- a/.sqlx/query-1a45960695ad8a4f60cd94b610f9cf0c9b09ea742ea07a8de4ef8e6b199f2269.json +++ b/.sqlx/query-0e9a8c2395d43898748c176db0d89fa20784cd93ca1dfd00b2755934937d8270.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "WITH stats AS ( SELECT DISTINCT ON (device_id) device_id, endpoint, latest_handshake FROM wireguard_peer_stats WHERE network = $1 ORDER BY device_id, collected_at DESC ) SELECT d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN wireguard_network_device wnd ON wnd.device_id = d.id LEFT JOIN stats on d.id = stats.device_id WHERE wnd.wireguard_network_id = $1 AND wnd.is_authorized = true AND (wnd.authorized_at IS NULL OR (NOW() - wnd.authorized_at) > $2 * interval '1 second') AND (stats.latest_handshake IS NULL OR (NOW() - stats.latest_handshake) > $2 * interval '1 second')", + "query": "WITH stats AS ( SELECT DISTINCT ON (device_id) device_id, endpoint, latest_handshake FROM wireguard_peer_stats WHERE network = $1 ORDER BY device_id, collected_at DESC ) SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN wireguard_network_device wnd ON wnd.device_id = d.id LEFT JOIN stats on d.id = stats.device_id WHERE wnd.wireguard_network_id = $1 AND wnd.is_authorized = true AND (wnd.authorized_at IS NULL OR (NOW() - wnd.authorized_at) > $2 * interval '1 second') AND (stats.latest_handshake IS NULL OR (NOW() - stats.latest_handshake) > $2 * interval '1 second')", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "1a45960695ad8a4f60cd94b610f9cf0c9b09ea742ea07a8de4ef8e6b199f2269" + "hash": "0e9a8c2395d43898748c176db0d89fa20784cd93ca1dfd00b2755934937d8270" } diff --git a/.sqlx/query-0f00cd80f489fe50957305cd0e2cfb992f473271cfa5f4e2cb8d9c92c4fea3f8.json b/.sqlx/query-0f00cd80f489fe50957305cd0e2cfb992f473271cfa5f4e2cb8d9c92c4fea3f8.json new file mode 100644 index 0000000000..c386fd3d79 --- /dev/null +++ b/.sqlx/query-0f00cd80f489fe50957305cd0e2cfb992f473271cfa5f4e2cb8d9c92c4fea3f8.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"pollingtoken\" SET \"token\" = $2,\"device_id\" = $3,\"created_at\" = $4 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Text", + "Int8", + "Timestamp" + ] + }, + "nullable": [] + }, + "hash": "0f00cd80f489fe50957305cd0e2cfb992f473271cfa5f4e2cb8d9c92c4fea3f8" +} diff --git a/.sqlx/query-efe773da199984a169b636cafabd4ee0967c7dc68a04ec930f863d8f3f34c14a.json b/.sqlx/query-1403aba7198c132812aa5642166e70c14b3c40a969b496972d499a975fb7b0b4.json similarity index 64% rename from .sqlx/query-efe773da199984a169b636cafabd4ee0967c7dc68a04ec930f863d8f3f34c14a.json rename to .sqlx/query-1403aba7198c132812aa5642166e70c14b3c40a969b496972d499a975fb7b0b4.json index 408dde1037..b753d65e6b 100644 --- a/.sqlx/query-efe773da199984a169b636cafabd4ee0967c7dc68a04ec930f863d8f3f34c14a.json +++ b/.sqlx/query-1403aba7198c132812aa5642166e70c14b3c40a969b496972d499a975fb7b0b4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH stats AS ( SELECT DISTINCT ON (network) network, endpoint, latest_handshake FROM wireguard_peer_stats WHERE device_id = $2 ORDER BY network, collected_at DESC ) SELECT n.id as network_id, n.name as network_name, n.endpoint as gateway_endpoint, wnd.wireguard_ip as \"device_wireguard_ip: IpAddr\", stats.endpoint as device_endpoint, stats.latest_handshake as \"latest_handshake?\", COALESCE (((NOW() - stats.latest_handshake) < $1 * interval '1 minute'), false) as \"is_active!\" FROM wireguard_network_device wnd JOIN wireguard_network n ON n.id = wnd.wireguard_network_id LEFT JOIN stats on n.id = stats.network WHERE wnd.device_id = $2", + "query": "WITH stats AS ( SELECT DISTINCT ON (network) network, endpoint, latest_handshake FROM wireguard_peer_stats WHERE device_id = $2 ORDER BY network, collected_at DESC ) SELECT n.id network_id, n.name network_name, n.endpoint gateway_endpoint, wnd.wireguard_ip \"device_wireguard_ip: IpAddr\", stats.endpoint device_endpoint, stats.latest_handshake \"latest_handshake?\", COALESCE (((NOW() - stats.latest_handshake) < $1 * interval '1 minute'), false) as \"is_active!\" FROM wireguard_network_device wnd JOIN wireguard_network n ON n.id = wnd.wireguard_network_id LEFT JOIN stats on n.id = stats.network WHERE wnd.device_id = $2", "describe": { "columns": [ { @@ -55,5 +55,5 @@ null ] }, - "hash": "efe773da199984a169b636cafabd4ee0967c7dc68a04ec930f863d8f3f34c14a" + "hash": "1403aba7198c132812aa5642166e70c14b3c40a969b496972d499a975fb7b0b4" } diff --git a/.sqlx/query-78766325753732e8873adc2b04bf73a3770887ba3b28c8568e71a7e4b83ab7a4.json b/.sqlx/query-18a1ea41a270bdf89e241948ca49d5a1bc27b5b1253d0c87d122355dbc0ff3cc.json similarity index 80% rename from .sqlx/query-78766325753732e8873adc2b04bf73a3770887ba3b28c8568e71a7e4b83ab7a4.json rename to .sqlx/query-18a1ea41a270bdf89e241948ca49d5a1bc27b5b1253d0c87d122355dbc0ff3cc.json index 2a9f91497b..c7dc27f073 100644 --- a/.sqlx/query-78766325753732e8873adc2b04bf73a3770887ba3b28c8568e71a7e4b83ab7a4.json +++ b/.sqlx/query-18a1ea41a270bdf89e241948ca49d5a1bc27b5b1253d0c87d122355dbc0ff3cc.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"client_id\",\"code\",\"redirect_uri\",\"scope\",\"auth_time\",\"nonce\",\"code_challenge\" FROM \"authorization_code\"", + "query": "SELECT id, \"user_id\",\"client_id\",\"code\",\"redirect_uri\",\"scope\",\"auth_time\",\"nonce\",\"code_challenge\" FROM \"authorization_code\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -64,5 +64,5 @@ true ] }, - "hash": "78766325753732e8873adc2b04bf73a3770887ba3b28c8568e71a7e4b83ab7a4" + "hash": "18a1ea41a270bdf89e241948ca49d5a1bc27b5b1253d0c87d122355dbc0ff3cc" } diff --git a/.sqlx/query-904ae3993de08b02a547db8d656a23d4a8e7e2d4bd556b1e2f60088b9f6fc679.json b/.sqlx/query-18d09bdf2b6780e6050a073a421df5397ed53cfba284942b402b8a0650f5a953.json similarity index 77% rename from .sqlx/query-904ae3993de08b02a547db8d656a23d4a8e7e2d4bd556b1e2f60088b9f6fc679.json rename to .sqlx/query-18d09bdf2b6780e6050a073a421df5397ed53cfba284942b402b8a0650f5a953.json index 6f2be25b68..2dbfacf39d 100644 --- a/.sqlx/query-904ae3993de08b02a547db8d656a23d4a8e7e2d4bd556b1e2f60088b9f6fc679.json +++ b/.sqlx/query-18d09bdf2b6780e6050a073a421df5397ed53cfba284942b402b8a0650f5a953.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1 AND client_secret = $2 AND enabled", + "query": "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1 AND client_secret = $2 AND enabled", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -55,5 +55,5 @@ false ] }, - "hash": "904ae3993de08b02a547db8d656a23d4a8e7e2d4bd556b1e2f60088b9f6fc679" + "hash": "18d09bdf2b6780e6050a073a421df5397ed53cfba284942b402b8a0650f5a953" } diff --git a/.sqlx/query-1aab945b0f7df1c079ae359b17b0629e7c30d4c7e6605157f8cb2bb1cfec322a.json b/.sqlx/query-1aab945b0f7df1c079ae359b17b0629e7c30d4c7e6605157f8cb2bb1cfec322a.json new file mode 100644 index 0000000000..eeb053bd99 --- /dev/null +++ b/.sqlx/query-1aab945b0f7df1c079ae359b17b0629e7c30d4c7e6605157f8cb2bb1cfec322a.json @@ -0,0 +1,125 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \"user\".id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" INNER JOIN \"group_user\" ON \"user\".id = \"group_user\".user_id INNER JOIN \"group\" ON \"group_user\".group_id = \"group\".id WHERE \"group\".name = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "password_hash", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "last_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "first_name", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "phone", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "mfa_enabled", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "totp_enabled", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "totp_secret", + "type_info": "Bytea" + }, + { + "ordinal": 10, + "name": "email_mfa_enabled", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "email_mfa_secret", + "type_info": "Bytea" + }, + { + "ordinal": 12, + "name": "mfa_method: _", + "type_info": { + "Custom": { + "name": "mfa_method", + "kind": { + "Enum": [ + "none", + "one_time_password", + "webauthn", + "web3", + "email" + ] + } + } + } + }, + { + "ordinal": 13, + "name": "recovery_codes", + "type_info": "TextArray" + }, + { + "ordinal": 14, + "name": "is_active", + "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "openid_sub", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + true, + false, + false, + true, + false, + true, + false, + false, + false, + true + ] + }, + "hash": "1aab945b0f7df1c079ae359b17b0629e7c30d4c7e6605157f8cb2bb1cfec322a" +} diff --git a/.sqlx/query-f5ed6899054f1915882741843733fc196d59c356d69c0d06e9782cb1bc73e6f3.json b/.sqlx/query-1b1f0a82cbbf2abbafb7cae6718fb50c0a8294bce9757431ca3a6a0505f39df3.json similarity index 81% rename from .sqlx/query-f5ed6899054f1915882741843733fc196d59c356d69c0d06e9782cb1bc73e6f3.json rename to .sqlx/query-1b1f0a82cbbf2abbafb7cae6718fb50c0a8294bce9757431ca3a6a0505f39df3.json index d4dfe1b6b1..e9efbe782b 100644 --- a/.sqlx/query-f5ed6899054f1915882741843733fc196d59c356d69c0d06e9782cb1bc73e6f3.json +++ b/.sqlx/query-1b1f0a82cbbf2abbafb7cae6718fb50c0a8294bce9757431ca3a6a0505f39df3.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active FROM \"user\" WHERE id = ANY($1)", + "query": "SELECT id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE id = ANY($1)", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -90,6 +90,11 @@ "ordinal": 14, "name": "is_active", "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "openid_sub", + "type_info": "Text" } ], "parameters": { @@ -112,8 +117,9 @@ true, false, false, - false + false, + true ] }, - "hash": "f5ed6899054f1915882741843733fc196d59c356d69c0d06e9782cb1bc73e6f3" + "hash": "1b1f0a82cbbf2abbafb7cae6718fb50c0a8294bce9757431ca3a6a0505f39df3" } diff --git a/.sqlx/query-3e6fa53cc900724e25e127f472cb0cb5b5e76fbcf424a79b94862f623ea975fc.json b/.sqlx/query-1cad18ee4393faed671f07a2f0faeff413376b8c4760ef86f5de6a7e74b7c858.json similarity index 76% rename from .sqlx/query-3e6fa53cc900724e25e127f472cb0cb5b5e76fbcf424a79b94862f623ea975fc.json rename to .sqlx/query-1cad18ee4393faed671f07a2f0faeff413376b8c4760ef86f5de6a7e74b7c858.json index 431b7f8faf..97dac02442 100644 --- a/.sqlx/query-3e6fa53cc900724e25e127f472cb0cb5b5e76fbcf424a79b94862f623ea975fc.json +++ b/.sqlx/query-1cad18ee4393faed671f07a2f0faeff413376b8c4760ef86f5de6a7e74b7c858.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created FROM device WHERE user_id = $1", + "query": "SELECT id, \"name\",\"wireguard_pubkey\",\"user_id\",\"created\" FROM \"device\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "3e6fa53cc900724e25e127f472cb0cb5b5e76fbcf424a79b94862f623ea975fc" + "hash": "1cad18ee4393faed671f07a2f0faeff413376b8c4760ef86f5de6a7e74b7c858" } diff --git a/.sqlx/query-443177d880a607fc5c545cad75db89ab8ca59b3b1bc9f5ed6847afd7fea9d68c.json b/.sqlx/query-227df2a09adeb2387780e7df5b270bd60d626294a6b9b855777641934d898d43.json similarity index 76% rename from .sqlx/query-443177d880a607fc5c545cad75db89ab8ca59b3b1bc9f5ed6847afd7fea9d68c.json rename to .sqlx/query-227df2a09adeb2387780e7df5b270bd60d626294a6b9b855777641934d898d43.json index 2efcfdf4d0..e168bad942 100644 --- a/.sqlx/query-443177d880a607fc5c545cad75db89ab8ca59b3b1bc9f5ed6847afd7fea9d68c.json +++ b/.sqlx/query-227df2a09adeb2387780e7df5b270bd60d626294a6b9b855777641934d898d43.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"client_id\",\"client_secret\",\"redirect_uri\" \"redirect_uri: _\",\"scope\" \"scope: _\",\"name\",\"enabled\" FROM \"oauth2client\" WHERE id = $1", + "query": "SELECT id, \"client_id\",\"client_secret\",\"redirect_uri\" \"redirect_uri: _\",\"scope\" \"scope: _\",\"name\",\"enabled\" FROM \"oauth2client\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -54,5 +54,5 @@ false ] }, - "hash": "443177d880a607fc5c545cad75db89ab8ca59b3b1bc9f5ed6847afd7fea9d68c" + "hash": "227df2a09adeb2387780e7df5b270bd60d626294a6b9b855777641934d898d43" } diff --git a/.sqlx/query-43ac4b3a375a4756d77b015c0bb871f6ccc4f5ecc7ec32f0b3a9f64398f3686e.json b/.sqlx/query-244d19b770616a733faa1626de860d1d6d5d836bf759a4747726000515b7ae3e.json similarity index 79% rename from .sqlx/query-43ac4b3a375a4756d77b015c0bb871f6ccc4f5ecc7ec32f0b3a9f64398f3686e.json rename to .sqlx/query-244d19b770616a733faa1626de860d1d6d5d836bf759a4747726000515b7ae3e.json index 0e0a82ef1b..48c3c62c8a 100644 --- a/.sqlx/query-43ac4b3a375a4756d77b015c0bb871f6ccc4f5ecc7ec32f0b3a9f64398f3686e.json +++ b/.sqlx/query-244d19b770616a733faa1626de860d1d6d5d836bf759a4747726000515b7ae3e.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\" \"mfa_method: _\",\"recovery_codes\" \"recovery_codes: _\" FROM \"user\"", + "query": "SELECT id, \"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"openid_sub\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\" \"mfa_method: _\",\"recovery_codes\" \"recovery_codes: _\" FROM \"user\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -50,26 +50,31 @@ }, { "ordinal": 9, + "name": "openid_sub", + "type_info": "Text" + }, + { + "ordinal": 10, "name": "totp_enabled", "type_info": "Bool" }, { - "ordinal": 10, + "ordinal": 11, "name": "email_mfa_enabled", "type_info": "Bool" }, { - "ordinal": 11, + "ordinal": 12, "name": "totp_secret", "type_info": "Bytea" }, { - "ordinal": 12, + "ordinal": 13, "name": "email_mfa_secret", "type_info": "Bytea" }, { - "ordinal": 13, + "ordinal": 14, "name": "mfa_method: _", "type_info": { "Custom": { @@ -87,7 +92,7 @@ } }, { - "ordinal": 14, + "ordinal": 15, "name": "recovery_codes: _", "type_info": "TextArray" } @@ -105,6 +110,7 @@ true, false, false, + true, false, false, true, @@ -113,5 +119,5 @@ false ] }, - "hash": "43ac4b3a375a4756d77b015c0bb871f6ccc4f5ecc7ec32f0b3a9f64398f3686e" + "hash": "244d19b770616a733faa1626de860d1d6d5d836bf759a4747726000515b7ae3e" } diff --git a/.sqlx/query-27fabf7f3fc353eea6f485d902dbbb5a1f83a01f0bacf7b50c2b5d11f6dd0fde.json b/.sqlx/query-27fabf7f3fc353eea6f485d902dbbb5a1f83a01f0bacf7b50c2b5d11f6dd0fde.json new file mode 100644 index 0000000000..bd0e7698f3 --- /dev/null +++ b/.sqlx/query-27fabf7f3fc353eea6f485d902dbbb5a1f83a01f0bacf7b50c2b5d11f6dd0fde.json @@ -0,0 +1,38 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, \"token\",\"device_id\",\"created_at\" FROM \"pollingtoken\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "token", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "device_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "27fabf7f3fc353eea6f485d902dbbb5a1f83a01f0bacf7b50c2b5d11f6dd0fde" +} diff --git a/.sqlx/query-18bffe90d894f5a122df97ef37c4afb69f97c74acf36ee7596476ab7fee850b3.json b/.sqlx/query-281722fbef2b32819f1022d99b8973eb7f1ae83a2d2a7406b8763687821a2c14.json similarity index 74% rename from .sqlx/query-18bffe90d894f5a122df97ef37c4afb69f97c74acf36ee7596476ab7fee850b3.json rename to .sqlx/query-281722fbef2b32819f1022d99b8973eb7f1ae83a2d2a7406b8763687821a2c14.json index 0902ed5b06..5110e6a875 100644 --- a/.sqlx/query-18bffe90d894f5a122df97ef37c4afb69f97c74acf36ee7596476ab7fee850b3.json +++ b/.sqlx/query-281722fbef2b32819f1022d99b8973eb7f1ae83a2d2a7406b8763687821a2c14.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, name, passkey FROM webauthn WHERE user_id = $1", + "query": "SELECT id, \"user_id\",\"name\",\"passkey\" FROM \"webauthn\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "18bffe90d894f5a122df97ef37c4afb69f97c74acf36ee7596476ab7fee850b3" + "hash": "281722fbef2b32819f1022d99b8973eb7f1ae83a2d2a7406b8763687821a2c14" } diff --git a/.sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json b/.sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json new file mode 100644 index 0000000000..c1696c5b03 --- /dev/null +++ b/.sqlx/query-283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT admin_device_management, disable_all_traffic, only_client_activation FROM \"enterprisesettings\" WHERE id = 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "admin_device_management", + "type_info": "Bool" + }, + { + "ordinal": 1, + "name": "disable_all_traffic", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "only_client_activation", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "283e1c3d082f1388fc2b806bdcab715db1c1df67da573b0f132fea265e42b416" +} diff --git a/.sqlx/query-2d84370e3cabed5f8578ef3934d7f3a3863f267eab921370d4db55cf97fea979.json b/.sqlx/query-2d84370e3cabed5f8578ef3934d7f3a3863f267eab921370d4db55cf97fea979.json new file mode 100644 index 0000000000..14798cf9dd --- /dev/null +++ b/.sqlx/query-2d84370e3cabed5f8578ef3934d7f3a3863f267eab921370d4db55cf97fea979.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"settings\" SET license = $1 WHERE id = 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "2d84370e3cabed5f8578ef3934d7f3a3863f267eab921370d4db55cf97fea979" +} diff --git a/.sqlx/query-1b29a6b1d3741ede2d85271f0f0e07069048ba01f8c3c5988f044e788200f9f8.json b/.sqlx/query-3017740cad14426beaca494cdf133e525b75984aa636f7cc8cc17a406e64b759.json similarity index 81% rename from .sqlx/query-1b29a6b1d3741ede2d85271f0f0e07069048ba01f8c3c5988f044e788200f9f8.json rename to .sqlx/query-3017740cad14426beaca494cdf133e525b75984aa636f7cc8cc17a406e64b759.json index ee2196861b..c64ac829cf 100644 --- a/.sqlx/query-1b29a6b1d3741ede2d85271f0f0e07069048ba01f8c3c5988f044e788200f9f8.json +++ b/.sqlx/query-3017740cad14426beaca494cdf133e525b75984aa636f7cc8cc17a406e64b759.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active FROM \"user\" WHERE username = $1", + "query": "SELECT id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE email = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -90,6 +90,11 @@ "ordinal": 14, "name": "is_active", "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "openid_sub", + "type_info": "Text" } ], "parameters": { @@ -112,8 +117,9 @@ true, false, false, - false + false, + true ] }, - "hash": "1b29a6b1d3741ede2d85271f0f0e07069048ba01f8c3c5988f044e788200f9f8" + "hash": "3017740cad14426beaca494cdf133e525b75984aa636f7cc8cc17a406e64b759" } diff --git a/.sqlx/query-e0d59d4aa01a863eca5f00470c70112fd9a78c4565fd8e44185dc31c9b894b9e.json b/.sqlx/query-321ce9355db39c3d93d8b915ba7888357f5403c2e9e97fa17e502223bbcc918a.json similarity index 81% rename from .sqlx/query-e0d59d4aa01a863eca5f00470c70112fd9a78c4565fd8e44185dc31c9b894b9e.json rename to .sqlx/query-321ce9355db39c3d93d8b915ba7888357f5403c2e9e97fa17e502223bbcc918a.json index bb2b4ad1e0..c4bb584637 100644 --- a/.sqlx/query-e0d59d4aa01a863eca5f00470c70112fd9a78c4565fd8e44185dc31c9b894b9e.json +++ b/.sqlx/query-321ce9355db39c3d93d8b915ba7888357f5403c2e9e97fa17e502223bbcc918a.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, client_id, code, redirect_uri, scope, auth_time, nonce, code_challenge FROM authorization_code WHERE code = $1", + "query": "SELECT id, user_id, client_id, code, redirect_uri, scope, auth_time, nonce, code_challenge FROM authorization_code WHERE code = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ true ] }, - "hash": "e0d59d4aa01a863eca5f00470c70112fd9a78c4565fd8e44185dc31c9b894b9e" + "hash": "321ce9355db39c3d93d8b915ba7888357f5403c2e9e97fa17e502223bbcc918a" } diff --git a/.sqlx/query-c4f0393fc5b1bb1603f1da3a6f2cb7e6253a0cb8fffd710e5a4d03cf9a4eef16.json b/.sqlx/query-342442c866249cb211377ad73d5e2cbd434104a0b0294ccdd2d0b4aedf611b18.json similarity index 74% rename from .sqlx/query-c4f0393fc5b1bb1603f1da3a6f2cb7e6253a0cb8fffd710e5a4d03cf9a4eef16.json rename to .sqlx/query-342442c866249cb211377ad73d5e2cbd434104a0b0294ccdd2d0b4aedf611b18.json index 2d73e7c9a7..4717ef83f5 100644 --- a/.sqlx/query-c4f0393fc5b1bb1603f1da3a6f2cb7e6253a0cb8fffd710e5a4d03cf9a4eef16.json +++ b/.sqlx/query-342442c866249cb211377ad73d5e2cbd434104a0b0294ccdd2d0b4aedf611b18.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"name\",\"passkey\" FROM \"webauthn\"", + "query": "SELECT id, \"user_id\",\"name\",\"passkey\" FROM \"webauthn\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -34,5 +34,5 @@ false ] }, - "hash": "c4f0393fc5b1bb1603f1da3a6f2cb7e6253a0cb8fffd710e5a4d03cf9a4eef16" + "hash": "342442c866249cb211377ad73d5e2cbd434104a0b0294ccdd2d0b4aedf611b18" } diff --git a/.sqlx/query-393cacddf87582e0a72156580765dfb15404030a4f28406cbaa344ab279300d7.json b/.sqlx/query-393cacddf87582e0a72156580765dfb15404030a4f28406cbaa344ab279300d7.json deleted file mode 100644 index 6fa96fee85..0000000000 --- a/.sqlx/query-393cacddf87582e0a72156580765dfb15404030a4f28406cbaa344ab279300d7.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT DISTINCT ON (d.id) d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN \"user\" u ON d.user_id = u.id JOIN group_user gu ON u.id = gu.user_id JOIN \"group\" g ON gu.group_id = g.id WHERE g.\"name\" IN (SELECT * FROM UNNEST($1::text[]))\n AND u.is_active = true\n ORDER BY d.id ASC", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "wireguard_pubkey", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "user_id", - "type_info": "Int8" - }, - { - "ordinal": 4, - "name": "created", - "type_info": "Timestamp" - } - ], - "parameters": { - "Left": [ - "TextArray" - ] - }, - "nullable": [ - false, - false, - false, - false, - false - ] - }, - "hash": "393cacddf87582e0a72156580765dfb15404030a4f28406cbaa344ab279300d7" -} diff --git a/.sqlx/query-f2a1c41dc09e84bd53764f118d120cff9be08cc24c6a6ef15bf404d239d919cc.json b/.sqlx/query-3c9f65c18fd93b02f5b01d13af7b984f1b2043615f2c5b2dafe5055ce27ee91b.json similarity index 80% rename from .sqlx/query-f2a1c41dc09e84bd53764f118d120cff9be08cc24c6a6ef15bf404d239d919cc.json rename to .sqlx/query-3c9f65c18fd93b02f5b01d13af7b984f1b2043615f2c5b2dafe5055ce27ee91b.json index 205c5e977f..671abb813f 100644 --- a/.sqlx/query-f2a1c41dc09e84bd53764f118d120cff9be08cc24c6a6ef15bf404d239d919cc.json +++ b/.sqlx/query-3c9f65c18fd93b02f5b01d13af7b984f1b2043615f2c5b2dafe5055ce27ee91b.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"yubikey_id\",\"name\",\"user_id\",\"key\",\"key_type\" \"key_type: _\" FROM \"authentication_key\"", + "query": "SELECT id, \"yubikey_id\",\"name\",\"user_id\",\"key\",\"key_type\" \"key_type: _\" FROM \"authentication_key\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -56,5 +56,5 @@ false ] }, - "hash": "f2a1c41dc09e84bd53764f118d120cff9be08cc24c6a6ef15bf404d239d919cc" + "hash": "3c9f65c18fd93b02f5b01d13af7b984f1b2043615f2c5b2dafe5055ce27ee91b" } diff --git a/.sqlx/query-ea43c7bfcd2de8f05d3db1123ddb2cd0652284d321d533454bb4f3e04d835904.json b/.sqlx/query-3d0439898529e2e68c87a1939ca331d564ed406f733c079b526f2a53031b859d.json similarity index 78% rename from .sqlx/query-ea43c7bfcd2de8f05d3db1123ddb2cd0652284d321d533454bb4f3e04d835904.json rename to .sqlx/query-3d0439898529e2e68c87a1939ca331d564ed406f733c079b526f2a53031b859d.json index 6bd9ca71dd..b92f12fbef 100644 --- a/.sqlx/query-ea43c7bfcd2de8f05d3db1123ddb2cd0652284d321d533454bb4f3e04d835904.json +++ b/.sqlx/query-3d0439898529e2e68c87a1939ca331d564ed406f733c079b526f2a53031b859d.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, ip_address, model, family, brand, os_family, browser, event_type, created\n FROM device_login_event WHERE user_id = $1 AND event_type = $2 AND family = $3 AND brand = $4 AND model = $5 AND browser = $6", + "query": "SELECT id, user_id, ip_address, model, family, brand, os_family, browser, event_type, created FROM device_login_event WHERE user_id = $1 AND event_type = $2 AND family = $3 AND brand = $4 AND model = $5 AND browser = $6", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -77,5 +77,5 @@ false ] }, - "hash": "ea43c7bfcd2de8f05d3db1123ddb2cd0652284d321d533454bb4f3e04d835904" + "hash": "3d0439898529e2e68c87a1939ca331d564ed406f733c079b526f2a53031b859d" } diff --git a/.sqlx/query-48ecfa6318149ac712807ca8fa0ec1fcb972f917729ffb9b7849830113d04bd8.json b/.sqlx/query-3e548b5ec5f39cdd2134434781061dc596a7c772b7332c2b008f31db6f2f10d5.json similarity index 73% rename from .sqlx/query-48ecfa6318149ac712807ca8fa0ec1fcb972f917729ffb9b7849830113d04bd8.json rename to .sqlx/query-3e548b5ec5f39cdd2134434781061dc596a7c772b7332c2b008f31db6f2f10d5.json index c79058797b..266b4ccf90 100644 --- a/.sqlx/query-48ecfa6318149ac712807ca8fa0ec1fcb972f917729ffb9b7849830113d04bd8.json +++ b/.sqlx/query-3e548b5ec5f39cdd2134434781061dc596a7c772b7332c2b008f31db6f2f10d5.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"name\",\"passkey\" FROM \"webauthn\" WHERE id = $1", + "query": "SELECT id, user_id, name, passkey FROM webauthn WHERE user_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "48ecfa6318149ac712807ca8fa0ec1fcb972f917729ffb9b7849830113d04bd8" + "hash": "3e548b5ec5f39cdd2134434781061dc596a7c772b7332c2b008f31db6f2f10d5" } diff --git a/.sqlx/query-84679835466cb41a74dd9ef281c9a69451102dae52ffb5a4df99e160a1ec8907.json b/.sqlx/query-3f615b93c20a7d1e2740f6d826b78b62cfb433385383ffc8358be1a14ce13470.json similarity index 68% rename from .sqlx/query-84679835466cb41a74dd9ef281c9a69451102dae52ffb5a4df99e160a1ec8907.json rename to .sqlx/query-3f615b93c20a7d1e2740f6d826b78b62cfb433385383ffc8358be1a14ce13470.json index 15003141bd..2fa7cce277 100644 --- a/.sqlx/query-84679835466cb41a74dd9ef281c9a69451102dae52ffb5a4df99e160a1ec8907.json +++ b/.sqlx/query-3f615b93c20a7d1e2740f6d826b78b62cfb433385383ffc8358be1a14ce13470.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT d.id \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN wireguard_network_device wnd ON d.id = wnd.device_id WHERE wnd.wireguard_ip = $1 AND wnd.wireguard_network_id = $2", + "query": "SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN wireguard_network_device wnd ON d.id = wnd.device_id WHERE wnd.wireguard_ip = $1 AND wnd.wireguard_network_id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "84679835466cb41a74dd9ef281c9a69451102dae52ffb5a4df99e160a1ec8907" + "hash": "3f615b93c20a7d1e2740f6d826b78b62cfb433385383ffc8358be1a14ce13470" } diff --git a/.sqlx/query-3d7def96ee88ac4f1c46ff6ef93b06b8acc1f4bc9e34ff1cca6d64d6d281c039.json b/.sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json similarity index 79% rename from .sqlx/query-3d7def96ee88ac4f1c46ff6ef93b06b8acc1f4bc9e34ff1cca6d64d6d281c039.json rename to .sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json index 0443a1d38b..eefbf825a7 100644 --- a/.sqlx/query-3d7def96ee88ac4f1c46ff6ef93b06b8acc1f4bc9e34ff1cca6d64d6d281c039.json +++ b/.sqlx/query-45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\"", + "query": "SELECT id, \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -64,5 +64,5 @@ true ] }, - "hash": "3d7def96ee88ac4f1c46ff6ef93b06b8acc1f4bc9e34ff1cca6d64d6d281c039" + "hash": "45ab60569d24c9f9859a68c022cfc2fb13ea09e95ba0cb86f5419a85147dbf97" } diff --git a/.sqlx/query-15b81097f9a7f208205df7d316288e7fc380e2585495ddb1b726b008fa28bbf7.json b/.sqlx/query-45dd29c416448dbccfb3c3206684309f0470814bd94bdf06d77fdfcf98a7f109.json similarity index 81% rename from .sqlx/query-15b81097f9a7f208205df7d316288e7fc380e2585495ddb1b726b008fa28bbf7.json rename to .sqlx/query-45dd29c416448dbccfb3c3206684309f0470814bd94bdf06d77fdfcf98a7f109.json index 94da9a5fed..131af67c99 100644 --- a/.sqlx/query-15b81097f9a7f208205df7d316288e7fc380e2585495ddb1b726b008fa28bbf7.json +++ b/.sqlx/query-45dd29c416448dbccfb3c3206684309f0470814bd94bdf06d77fdfcf98a7f109.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"ip_address\",\"model\",\"family\",\"brand\",\"os_family\",\"browser\",\"event_type\",\"created\" FROM \"device_login_event\"", + "query": "SELECT id, \"user_id\",\"ip_address\",\"model\",\"family\",\"brand\",\"os_family\",\"browser\",\"event_type\",\"created\" FROM \"device_login_event\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -70,5 +70,5 @@ false ] }, - "hash": "15b81097f9a7f208205df7d316288e7fc380e2585495ddb1b726b008fa28bbf7" + "hash": "45dd29c416448dbccfb3c3206684309f0470814bd94bdf06d77fdfcf98a7f109" } diff --git a/.sqlx/query-06ecfe6d6dc628eaa2048071fc019f2c5100d73bee6759728af5d707fc68d725.json b/.sqlx/query-4b9ef60e8d5f1d361a51981740f470cbfe9779a169c432ec8f73efd8b69b082e.json similarity index 80% rename from .sqlx/query-06ecfe6d6dc628eaa2048071fc019f2c5100d73bee6759728af5d707fc68d725.json rename to .sqlx/query-4b9ef60e8d5f1d361a51981740f470cbfe9779a169c432ec8f73efd8b69b082e.json index e445e9c722..1e2eb1cadd 100644 --- a/.sqlx/query-06ecfe6d6dc628eaa2048071fc019f2c5100d73bee6759728af5d707fc68d725.json +++ b/.sqlx/query-4b9ef60e8d5f1d361a51981740f470cbfe9779a169c432ec8f73efd8b69b082e.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"ip_address\",\"model\",\"family\",\"brand\",\"os_family\",\"browser\",\"event_type\",\"created\" FROM \"device_login_event\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"ip_address\",\"model\",\"family\",\"brand\",\"os_family\",\"browser\",\"event_type\",\"created\" FROM \"device_login_event\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -72,5 +72,5 @@ false ] }, - "hash": "06ecfe6d6dc628eaa2048071fc019f2c5100d73bee6759728af5d707fc68d725" + "hash": "4b9ef60e8d5f1d361a51981740f470cbfe9779a169c432ec8f73efd8b69b082e" } diff --git a/.sqlx/query-63dd22326d77a452d5624d378b0653a7b6b98d71caaf55c6651a44bbd57df017.json b/.sqlx/query-4d43391c1eda0e6e74187d3c7ade0a852264d7465295de0223e00cf1f69c98c1.json similarity index 70% rename from .sqlx/query-63dd22326d77a452d5624d378b0653a7b6b98d71caaf55c6651a44bbd57df017.json rename to .sqlx/query-4d43391c1eda0e6e74187d3c7ade0a852264d7465295de0223e00cf1f69c98c1.json index 758eb41133..e63c11d965 100644 --- a/.sqlx/query-63dd22326d77a452d5624d378b0653a7b6b98d71caaf55c6651a44bbd57df017.json +++ b/.sqlx/query-4d43391c1eda0e6e74187d3c7ade0a852264d7465295de0223e00cf1f69c98c1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT wireguard_network_id as network_id, wireguard_ip as \"device_wireguard_ip: IpAddr\", preshared_key, is_authorized FROM wireguard_network_device WHERE device_id = $1", + "query": "SELECT wireguard_network_id network_id, wireguard_ip \"device_wireguard_ip: IpAddr\", preshared_key, is_authorized FROM wireguard_network_device WHERE device_id = $1", "describe": { "columns": [ { @@ -36,5 +36,5 @@ false ] }, - "hash": "63dd22326d77a452d5624d378b0653a7b6b98d71caaf55c6651a44bbd57df017" + "hash": "4d43391c1eda0e6e74187d3c7ade0a852264d7465295de0223e00cf1f69c98c1" } diff --git a/.sqlx/query-15d8aae5b2f7cbc1c415f8e589b12c762f8f7f386beea4543f96f471814a6929.json b/.sqlx/query-4fd61ae53fb5cf6ccb74d124871049b180c6a873393cd229396074549b742a59.json similarity index 69% rename from .sqlx/query-15d8aae5b2f7cbc1c415f8e589b12c762f8f7f386beea4543f96f471814a6929.json rename to .sqlx/query-4fd61ae53fb5cf6ccb74d124871049b180c6a873393cd229396074549b742a59.json index 84e74c0c31..e87c246beb 100644 --- a/.sqlx/query-15d8aae5b2f7cbc1c415f8e589b12c762f8f7f386beea4543f96f471814a6929.json +++ b/.sqlx/query-4fd61ae53fb5cf6ccb74d124871049b180c6a873393cd229396074549b742a59.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"oauth2client_id\" FROM \"oauth2authorizedapp\"", + "query": "SELECT id, \"user_id\",\"oauth2client_id\" FROM \"oauth2authorizedapp\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -28,5 +28,5 @@ false ] }, - "hash": "15d8aae5b2f7cbc1c415f8e589b12c762f8f7f386beea4543f96f471814a6929" + "hash": "4fd61ae53fb5cf6ccb74d124871049b180c6a873393cd229396074549b742a59" } diff --git a/.sqlx/query-51986b429728226f4c2b17d0af4eb0baed9bb96f3d19b38d9573b8a4422155d9.json b/.sqlx/query-55568f51eda479e3cdaeefd641802ccf6cdcebe76c12cde524b162552b002d89.json similarity index 81% rename from .sqlx/query-51986b429728226f4c2b17d0af4eb0baed9bb96f3d19b38d9573b8a4422155d9.json rename to .sqlx/query-55568f51eda479e3cdaeefd641802ccf6cdcebe76c12cde524b162552b002d89.json index 664cf4d354..819a0e66e0 100644 --- a/.sqlx/query-51986b429728226f4c2b17d0af4eb0baed9bb96f3d19b38d9573b8a4422155d9.json +++ b/.sqlx/query-55568f51eda479e3cdaeefd641802ccf6cdcebe76c12cde524b162552b002d89.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE wireguard_network_id = $1", + "query": "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE wireguard_network_id = $1", "describe": { "columns": [ { @@ -48,5 +48,5 @@ true ] }, - "hash": "51986b429728226f4c2b17d0af4eb0baed9bb96f3d19b38d9573b8a4422155d9" + "hash": "55568f51eda479e3cdaeefd641802ccf6cdcebe76c12cde524b162552b002d89" } diff --git a/.sqlx/query-1a01b8b88444b493abf74b2ec0ad649018244de3c7f23d98bfab71faa1a9fae1.json b/.sqlx/query-5678d744e1e6ba8e7301a3b9e200b61fcc18530d40e13549b4b0d99ed10cb86c.json similarity index 82% rename from .sqlx/query-1a01b8b88444b493abf74b2ec0ad649018244de3c7f23d98bfab71faa1a9fae1.json rename to .sqlx/query-5678d744e1e6ba8e7301a3b9e200b61fcc18530d40e13549b4b0d99ed10cb86c.json index c6790b55f4..bb4d744b85 100644 --- a/.sqlx/query-1a01b8b88444b493abf74b2ec0ad649018244de3c7f23d98bfab71faa1a9fae1.json +++ b/.sqlx/query-5678d744e1e6ba8e7301a3b9e200b61fcc18530d40e13549b4b0d99ed10cb86c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id as \"id?\", name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold FROM wireguard_network WHERE mfa_enabled = true", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold FROM wireguard_network WHERE mfa_enabled = true", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -88,5 +88,5 @@ false ] }, - "hash": "1a01b8b88444b493abf74b2ec0ad649018244de3c7f23d98bfab71faa1a9fae1" + "hash": "5678d744e1e6ba8e7301a3b9e200b61fcc18530d40e13549b4b0d99ed10cb86c" } diff --git a/.sqlx/query-7acf1bf5bd1597e01908c3a9e4d5540db061f77d69de853abe8e5f09149dfdf8.json b/.sqlx/query-591aa58e7b79ac54309941efbe447b945d933569289803126d906adf32a56b29.json similarity index 67% rename from .sqlx/query-7acf1bf5bd1597e01908c3a9e4d5540db061f77d69de853abe8e5f09149dfdf8.json rename to .sqlx/query-591aa58e7b79ac54309941efbe447b945d933569289803126d906adf32a56b29.json index 55fb6f943b..b7ab4a77c4 100644 --- a/.sqlx/query-7acf1bf5bd1597e01908c3a9e4d5540db061f77d69de853abe8e5f09149dfdf8.json +++ b/.sqlx/query-591aa58e7b79ac54309941efbe447b945d933569289803126d906adf32a56b29.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, oauth2client_id FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = $2", + "query": "SELECT id, user_id, oauth2client_id FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -31,5 +31,5 @@ false ] }, - "hash": "7acf1bf5bd1597e01908c3a9e4d5540db061f77d69de853abe8e5f09149dfdf8" + "hash": "591aa58e7b79ac54309941efbe447b945d933569289803126d906adf32a56b29" } diff --git a/.sqlx/query-8c69910bf01556b4e35a26a4bd583d1694b8053c8522e2e6a311e1e1b4b4de15.json b/.sqlx/query-59bf54c747b02c1611b71028e662634bb056938becc3eb63fc60c661a2afaab0.json similarity index 59% rename from .sqlx/query-8c69910bf01556b4e35a26a4bd583d1694b8053c8522e2e6a311e1e1b4b4de15.json rename to .sqlx/query-59bf54c747b02c1611b71028e662634bb056938becc3eb63fc60c661a2afaab0.json index 4aef0c1ba1..aa10043a85 100644 --- a/.sqlx/query-8c69910bf01556b4e35a26a4bd583d1694b8053c8522e2e6a311e1e1b4b4de15.json +++ b/.sqlx/query-59bf54c747b02c1611b71028e662634bb056938becc3eb63fc60c661a2afaab0.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id WHERE group_user.user_id = $1", + "query": "SELECT id, name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id WHERE group_user.user_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -24,5 +24,5 @@ false ] }, - "hash": "8c69910bf01556b4e35a26a4bd583d1694b8053c8522e2e6a311e1e1b4b4de15" + "hash": "59bf54c747b02c1611b71028e662634bb056938becc3eb63fc60c661a2afaab0" } diff --git a/.sqlx/query-439bef62ccc846cccca2c6979e5698a7aaba2beb19645b720eefa73e4dcac942.json b/.sqlx/query-5b4f2b3fabb2afc84764dfb06714823cb9b6fccbf717f531c09f0c8e8d0326e2.json similarity index 70% rename from .sqlx/query-439bef62ccc846cccca2c6979e5698a7aaba2beb19645b720eefa73e4dcac942.json rename to .sqlx/query-5b4f2b3fabb2afc84764dfb06714823cb9b6fccbf717f531c09f0c8e8d0326e2.json index b5ca2d9315..272e72966b 100644 --- a/.sqlx/query-439bef62ccc846cccca2c6979e5698a7aaba2beb19645b720eefa73e4dcac942.json +++ b/.sqlx/query-5b4f2b3fabb2afc84764dfb06714823cb9b6fccbf717f531c09f0c8e8d0326e2.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE device.id = $1 AND \"user\".username = $2", + "query": "SELECT device.id, name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE device.id = $1 AND \"user\".username = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "439bef62ccc846cccca2c6979e5698a7aaba2beb19645b720eefa73e4dcac942" + "hash": "5b4f2b3fabb2afc84764dfb06714823cb9b6fccbf717f531c09f0c8e8d0326e2" } diff --git a/.sqlx/query-5df51ef040adc83fbfbe3c243dc4574b1ab955c7a9f8064996aae94f8752daec.json b/.sqlx/query-5df51ef040adc83fbfbe3c243dc4574b1ab955c7a9f8064996aae94f8752daec.json new file mode 100644 index 0000000000..3b325e35cb --- /dev/null +++ b/.sqlx/query-5df51ef040adc83fbfbe3c243dc4574b1ab955c7a9f8064996aae94f8752daec.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, token, device_id, created_at FROM pollingtoken WHERE token = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "token", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "device_id", + "type_info": "Int8" + }, + { + "ordinal": 3, + "name": "created_at", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "5df51ef040adc83fbfbe3c243dc4574b1ab955c7a9f8064996aae94f8752daec" +} diff --git a/.sqlx/query-b6c2a4ad60b6ec057e3d9ccda459fe361bec61cddd4d57dbaa32306aab754e7c.json b/.sqlx/query-5e6153dff9ca15df757c9fb79eeafca7785a03923c43adec1ea7bf3035a32f3c.json similarity index 78% rename from .sqlx/query-b6c2a4ad60b6ec057e3d9ccda459fe361bec61cddd4d57dbaa32306aab754e7c.json rename to .sqlx/query-5e6153dff9ca15df757c9fb79eeafca7785a03923c43adec1ea7bf3035a32f3c.json index e8d5491810..b66bb8723e 100644 --- a/.sqlx/query-b6c2a4ad60b6ec057e3d9ccda459fe361bec61cddd4d57dbaa32306aab754e7c.json +++ b/.sqlx/query-5e6153dff9ca15df757c9fb79eeafca7785a03923c43adec1ea7bf3035a32f3c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, yubikey_id \"yubikey_id?\", key, name, key_type \"key_type: AuthenticationKeyType\" FROM authentication_key WHERE user_id = $1", + "query": "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, name, key_type \"key_type: AuthenticationKeyType\" FROM authentication_key WHERE user_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -58,5 +58,5 @@ false ] }, - "hash": "b6c2a4ad60b6ec057e3d9ccda459fe361bec61cddd4d57dbaa32306aab754e7c" + "hash": "5e6153dff9ca15df757c9fb79eeafca7785a03923c43adec1ea7bf3035a32f3c" } diff --git a/.sqlx/query-9e3c1c1f52bc1a576012c57c7472f9f7601e1128b15fc0ecc7676cd5aa01c88c.json b/.sqlx/query-60689e3ad76c36ed3fed76d77244de90fda17abfee564c480274bd4a1446fb37.json similarity index 71% rename from .sqlx/query-9e3c1c1f52bc1a576012c57c7472f9f7601e1128b15fc0ecc7676cd5aa01c88c.json rename to .sqlx/query-60689e3ad76c36ed3fed76d77244de90fda17abfee564c480274bd4a1446fb37.json index 82874b3d70..ae5f88eaea 100644 --- a/.sqlx/query-9e3c1c1f52bc1a576012c57c7472f9f7601e1128b15fc0ecc7676cd5aa01c88c.json +++ b/.sqlx/query-60689e3ad76c36ed3fed76d77244de90fda17abfee564c480274bd4a1446fb37.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE device.id = $1 AND \"user\".id = $2", + "query": "SELECT device.id, name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE device.id = $1 AND \"user\".id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "9e3c1c1f52bc1a576012c57c7472f9f7601e1128b15fc0ecc7676cd5aa01c88c" + "hash": "60689e3ad76c36ed3fed76d77244de90fda17abfee564c480274bd4a1446fb37" } diff --git a/.sqlx/query-59ff9c1093cb41a5f902cf42f92afbac37e0c581e96285340196841504ed3cf0.json b/.sqlx/query-64d373fbed97c81654689a8fbb26ffe854693a0d4ae16000242ef8db9a864b84.json similarity index 70% rename from .sqlx/query-59ff9c1093cb41a5f902cf42f92afbac37e0c581e96285340196841504ed3cf0.json rename to .sqlx/query-64d373fbed97c81654689a8fbb26ffe854693a0d4ae16000242ef8db9a864b84.json index 1f364cea31..e7f4770ad8 100644 --- a/.sqlx/query-59ff9c1093cb41a5f902cf42f92afbac37e0c581e96285340196841504ed3cf0.json +++ b/.sqlx/query-64d373fbed97c81654689a8fbb26ffe854693a0d4ae16000242ef8db9a864b84.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN \"user\" u ON d.user_id = u.id WHERE u.is_active = true ORDER BY d.id ASC", + "query": "SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN \"user\" u ON d.user_id = u.id WHERE u.is_active = true ORDER BY d.id ASC", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -40,5 +40,5 @@ false ] }, - "hash": "59ff9c1093cb41a5f902cf42f92afbac37e0c581e96285340196841504ed3cf0" + "hash": "64d373fbed97c81654689a8fbb26ffe854693a0d4ae16000242ef8db9a864b84" } diff --git a/.sqlx/query-2b9b3aa210fa00d43bece1acbd3ae490fea37010d98f3eff2ba15f50e8a6d7a9.json b/.sqlx/query-69ddae4f160f29172f491b926ffa357cf56d6a61c792c619b3d127bf285ff680.json similarity index 79% rename from .sqlx/query-2b9b3aa210fa00d43bece1acbd3ae490fea37010d98f3eff2ba15f50e8a6d7a9.json rename to .sqlx/query-69ddae4f160f29172f491b926ffa357cf56d6a61c792c619b3d127bf285ff680.json index c3182f082b..ec51e42ffd 100644 --- a/.sqlx/query-2b9b3aa210fa00d43bece1acbd3ae490fea37010d98f3eff2ba15f50e8a6d7a9.json +++ b/.sqlx/query-69ddae4f160f29172f491b926ffa357cf56d6a61c792c619b3d127bf285ff680.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\" \"mfa_method: _\",\"recovery_codes\" \"recovery_codes: _\" FROM \"user\" WHERE id = $1", + "query": "SELECT id, \"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"openid_sub\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\" \"mfa_method: _\",\"recovery_codes\" \"recovery_codes: _\" FROM \"user\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -50,26 +50,31 @@ }, { "ordinal": 9, + "name": "openid_sub", + "type_info": "Text" + }, + { + "ordinal": 10, "name": "totp_enabled", "type_info": "Bool" }, { - "ordinal": 10, + "ordinal": 11, "name": "email_mfa_enabled", "type_info": "Bool" }, { - "ordinal": 11, + "ordinal": 12, "name": "totp_secret", "type_info": "Bytea" }, { - "ordinal": 12, + "ordinal": 13, "name": "email_mfa_secret", "type_info": "Bytea" }, { - "ordinal": 13, + "ordinal": 14, "name": "mfa_method: _", "type_info": { "Custom": { @@ -87,7 +92,7 @@ } }, { - "ordinal": 14, + "ordinal": 15, "name": "recovery_codes: _", "type_info": "TextArray" } @@ -107,6 +112,7 @@ true, false, false, + true, false, false, true, @@ -115,5 +121,5 @@ false ] }, - "hash": "2b9b3aa210fa00d43bece1acbd3ae490fea37010d98f3eff2ba15f50e8a6d7a9" + "hash": "69ddae4f160f29172f491b926ffa357cf56d6a61c792c619b3d127bf285ff680" } diff --git a/.sqlx/query-6ae26abc026e5fe59e8505b2563fca6caf4a42bb9ad01d6f44db41a572abce95.json b/.sqlx/query-6ae26abc026e5fe59e8505b2563fca6caf4a42bb9ad01d6f44db41a572abce95.json new file mode 100644 index 0000000000..cc029dcf7e --- /dev/null +++ b/.sqlx/query-6ae26abc026e5fe59e8505b2563fca6caf4a42bb9ad01d6f44db41a572abce95.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"openidprovider\" SET \"name\" = $2,\"base_url\" = $3,\"client_id\" = $4,\"client_secret\" = $5 WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "6ae26abc026e5fe59e8505b2563fca6caf4a42bb9ad01d6f44db41a572abce95" +} diff --git a/.sqlx/query-b2410cc60e0e1dcacd03050d2a3ac417ca5822beebe08be3f23cc0e217e27740.json b/.sqlx/query-6fde26b448d0a4783060bf3e606e16931c0b71a91c88025eb1316287bd1f9d8c.json similarity index 67% rename from .sqlx/query-b2410cc60e0e1dcacd03050d2a3ac417ca5822beebe08be3f23cc0e217e27740.json rename to .sqlx/query-6fde26b448d0a4783060bf3e606e16931c0b71a91c88025eb1316287bd1f9d8c.json index bb5e7f6ade..5ba207612e 100644 --- a/.sqlx/query-b2410cc60e0e1dcacd03050d2a3ac417ca5822beebe08be3f23cc0e217e27740.json +++ b/.sqlx/query-6fde26b448d0a4783060bf3e606e16931c0b71a91c88025eb1316287bd1f9d8c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\" FROM \"group\" WHERE id = $1", + "query": "SELECT id, \"name\" FROM \"group\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -24,5 +24,5 @@ false ] }, - "hash": "b2410cc60e0e1dcacd03050d2a3ac417ca5822beebe08be3f23cc0e217e27740" + "hash": "6fde26b448d0a4783060bf3e606e16931c0b71a91c88025eb1316287bd1f9d8c" } diff --git a/.sqlx/query-72958cb69e1b737ca4a3eb2915bbf8bde2ff8f3db10090cef071aeb932c99ab2.json b/.sqlx/query-72958cb69e1b737ca4a3eb2915bbf8bde2ff8f3db10090cef071aeb932c99ab2.json new file mode 100644 index 0000000000..46c6f250e0 --- /dev/null +++ b/.sqlx/query-72958cb69e1b737ca4a3eb2915bbf8bde2ff8f3db10090cef071aeb932c99ab2.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, name, base_url, client_id, client_secret FROM openidprovider WHERE name = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "base_url", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "client_id", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "client_secret", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "72958cb69e1b737ca4a3eb2915bbf8bde2ff8f3db10090cef071aeb932c99ab2" +} diff --git a/.sqlx/query-0377049270e6dcc4f0ec3571bd998dcdbab8366524ff1f20f48a208b9f7dd9c6.json b/.sqlx/query-77291d31a566e9deca452048a933ffce58f7d086e51d05e491333c0d55f6edb4.json similarity index 78% rename from .sqlx/query-0377049270e6dcc4f0ec3571bd998dcdbab8366524ff1f20f48a208b9f7dd9c6.json rename to .sqlx/query-77291d31a566e9deca452048a933ffce58f7d086e51d05e491333c0d55f6edb4.json index 73871505ab..ac9724a83a 100644 --- a/.sqlx/query-0377049270e6dcc4f0ec3571bd998dcdbab8366524ff1f20f48a208b9f7dd9c6.json +++ b/.sqlx/query-77291d31a566e9deca452048a933ffce58f7d086e51d05e491333c0d55f6edb4.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1 AND enabled", + "query": "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1 AND enabled", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -54,5 +54,5 @@ false ] }, - "hash": "0377049270e6dcc4f0ec3571bd998dcdbab8366524ff1f20f48a208b9f7dd9c6" + "hash": "77291d31a566e9deca452048a933ffce58f7d086e51d05e491333c0d55f6edb4" } diff --git a/.sqlx/query-7244c9c8e437770235fa6cdb48e66719abf054cae9a35f833b8418d974aa45e1.json b/.sqlx/query-772a39361c7e13ff5b4d21475271ebef14485356272da5674e2eb5ea76916dfc.json similarity index 79% rename from .sqlx/query-7244c9c8e437770235fa6cdb48e66719abf054cae9a35f833b8418d974aa45e1.json rename to .sqlx/query-772a39361c7e13ff5b4d21475271ebef14485356272da5674e2eb5ea76916dfc.json index d4010f8f02..5455837c69 100644 --- a/.sqlx/query-7244c9c8e437770235fa6cdb48e66719abf054cae9a35f833b8418d974aa45e1.json +++ b/.sqlx/query-772a39361c7e13ff5b4d21475271ebef14485356272da5674e2eb5ea76916dfc.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"client_id\",\"code\",\"redirect_uri\",\"scope\",\"auth_time\",\"nonce\",\"code_challenge\" FROM \"authorization_code\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"client_id\",\"code\",\"redirect_uri\",\"scope\",\"auth_time\",\"nonce\",\"code_challenge\" FROM \"authorization_code\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ true ] }, - "hash": "7244c9c8e437770235fa6cdb48e66719abf054cae9a35f833b8418d974aa45e1" + "hash": "772a39361c7e13ff5b4d21475271ebef14485356272da5674e2eb5ea76916dfc" } diff --git a/.sqlx/query-3063672b53bf0bada26d6d1b0adbebfbeb42e6a4949a10b2f23ea4d968aeb736.json b/.sqlx/query-7a5b9fab7d61d39f7d886bf666e4cf783748131a945a943ffb5c9736f17070af.json similarity index 70% rename from .sqlx/query-3063672b53bf0bada26d6d1b0adbebfbeb42e6a4949a10b2f23ea4d968aeb736.json rename to .sqlx/query-7a5b9fab7d61d39f7d886bf666e4cf783748131a945a943ffb5c9736f17070af.json index e4f3bf2de0..0307d1aef4 100644 --- a/.sqlx/query-3063672b53bf0bada26d6d1b0adbebfbeb42e6a4949a10b2f23ea4d968aeb736.json +++ b/.sqlx/query-7a5b9fab7d61d39f7d886bf666e4cf783748131a945a943ffb5c9736f17070af.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE \"user\" SET \"username\" = $2,\"password_hash\" = $3,\"last_name\" = $4,\"first_name\" = $5,\"email\" = $6,\"phone\" = $7,\"mfa_enabled\" = $8,\"is_active\" = $9,\"totp_enabled\" = $10,\"email_mfa_enabled\" = $11,\"totp_secret\" = $12,\"email_mfa_secret\" = $13,\"mfa_method\" = $14,\"recovery_codes\" = $15 WHERE id = $1", + "query": "UPDATE \"user\" SET \"username\" = $2,\"password_hash\" = $3,\"last_name\" = $4,\"first_name\" = $5,\"email\" = $6,\"phone\" = $7,\"mfa_enabled\" = $8,\"is_active\" = $9,\"openid_sub\" = $10,\"totp_enabled\" = $11,\"email_mfa_enabled\" = $12,\"totp_secret\" = $13,\"email_mfa_secret\" = $14,\"mfa_method\" = $15,\"recovery_codes\" = $16 WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -14,6 +14,7 @@ "Text", "Bool", "Bool", + "Text", "Bool", "Bool", "Bytea", @@ -37,5 +38,5 @@ }, "nullable": [] }, - "hash": "3063672b53bf0bada26d6d1b0adbebfbeb42e6a4949a10b2f23ea4d968aeb736" + "hash": "7a5b9fab7d61d39f7d886bf666e4cf783748131a945a943ffb5c9736f17070af" } diff --git a/.sqlx/query-7ce8c80bce6f2ed44b7abaa53e65f6ff40beeeabdd4d88a393dd9986ab59934b.json b/.sqlx/query-7ce8c80bce6f2ed44b7abaa53e65f6ff40beeeabdd4d88a393dd9986ab59934b.json deleted file mode 100644 index de92e46380..0000000000 --- a/.sqlx/query-7ce8c80bce6f2ed44b7abaa53e65f6ff40beeeabdd4d88a393dd9986ab59934b.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO \"settings\" (\"openid_enabled\",\"wireguard_enabled\",\"webhooks_enabled\",\"worker_enabled\",\"challenge_template\",\"instance_name\",\"main_logo_url\",\"nav_logo_url\",\"smtp_server\",\"smtp_port\",\"smtp_encryption\",\"smtp_user\",\"smtp_password\",\"smtp_sender\",\"enrollment_vpn_step_optional\",\"enrollment_welcome_message\",\"enrollment_welcome_email\",\"enrollment_welcome_email_subject\",\"enrollment_use_welcome_message_as_email\",\"uuid\",\"ldap_url\",\"ldap_bind_username\",\"ldap_bind_password\",\"ldap_group_search_base\",\"ldap_user_search_base\",\"ldap_user_obj_class\",\"ldap_group_obj_class\",\"ldap_username_attr\",\"ldap_groupname_attr\",\"ldap_group_member_attr\",\"ldap_member_attr\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31) RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Bool", - "Bool", - "Bool", - "Bool", - "Text", - "Text", - "Text", - "Text", - "Text", - "Int4", - { - "Custom": { - "name": "smtp_encryption", - "kind": { - "Enum": [ - "none", - "starttls", - "implicittls" - ] - } - } - }, - "Text", - "Text", - "Text", - "Bool", - "Text", - "Text", - "Text", - "Bool", - "Uuid", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "7ce8c80bce6f2ed44b7abaa53e65f6ff40beeeabdd4d88a393dd9986ab59934b" -} diff --git a/.sqlx/query-7cf5b6245bab3bdf58bce20a791db4217a168edb014ce3bd9fd092b21553cdc5.json b/.sqlx/query-7cf5b6245bab3bdf58bce20a791db4217a168edb014ce3bd9fd092b21553cdc5.json new file mode 100644 index 0000000000..1bb2d632ff --- /dev/null +++ b/.sqlx/query-7cf5b6245bab3bdf58bce20a791db4217a168edb014ce3bd9fd092b21553cdc5.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM \"openidprovider\" WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + }, + "hash": "7cf5b6245bab3bdf58bce20a791db4217a168edb014ce3bd9fd092b21553cdc5" +} diff --git a/.sqlx/query-45a53587f6d8bee3de695b846d5bf85dbcf3fb1a202611480af6a7b9e28d864f.json b/.sqlx/query-7e10835be17818738155cb84070d878013fe50819f88285ac6f2606b42a3dd3d.json similarity index 83% rename from .sqlx/query-45a53587f6d8bee3de695b846d5bf85dbcf3fb1a202611480af6a7b9e28d864f.json rename to .sqlx/query-7e10835be17818738155cb84070d878013fe50819f88285ac6f2606b42a3dd3d.json index 57db8db093..3d89185760 100644 --- a/.sqlx/query-45a53587f6d8bee3de695b846d5bf85dbcf3fb1a202611480af6a7b9e28d864f.json +++ b/.sqlx/query-7e10835be17818738155cb84070d878013fe50819f88285ac6f2606b42a3dd3d.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id as \"id?\", name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold FROM wireguard_network WHERE name = $1", + "query": "SELECT id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold FROM wireguard_network WHERE name = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -90,5 +90,5 @@ false ] }, - "hash": "45a53587f6d8bee3de695b846d5bf85dbcf3fb1a202611480af6a7b9e28d864f" + "hash": "7e10835be17818738155cb84070d878013fe50819f88285ac6f2606b42a3dd3d" } diff --git a/.sqlx/query-838909479f3e9a1538980e1299be80b362b9ca168bdc2a4f10db6157b93b53e1.json b/.sqlx/query-838909479f3e9a1538980e1299be80b362b9ca168bdc2a4f10db6157b93b53e1.json new file mode 100644 index 0000000000..f539daaee7 --- /dev/null +++ b/.sqlx/query-838909479f3e9a1538980e1299be80b362b9ca168bdc2a4f10db6157b93b53e1.json @@ -0,0 +1,24 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO \"pollingtoken\" (\"token\",\"device_id\",\"created_at\") VALUES ($1,$2,$3) RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Int8", + "Timestamp" + ] + }, + "nullable": [ + false + ] + }, + "hash": "838909479f3e9a1538980e1299be80b362b9ca168bdc2a4f10db6157b93b53e1" +} diff --git a/.sqlx/query-b773bf99f9e3aafcade8bf57c6f9a49107df5e8d1452796c0bb4f2f657e2ec77.json b/.sqlx/query-865ffb2d7ee9809eaaac5bb7ad64371f0d066d89e565f72beed5965785dccfd2.json similarity index 55% rename from .sqlx/query-b773bf99f9e3aafcade8bf57c6f9a49107df5e8d1452796c0bb4f2f657e2ec77.json rename to .sqlx/query-865ffb2d7ee9809eaaac5bb7ad64371f0d066d89e565f72beed5965785dccfd2.json index 3c480bc095..969addc3ca 100644 --- a/.sqlx/query-b773bf99f9e3aafcade8bf57c6f9a49107df5e8d1452796c0bb4f2f657e2ec77.json +++ b/.sqlx/query-865ffb2d7ee9809eaaac5bb7ad64371f0d066d89e565f72beed5965785dccfd2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT d.wireguard_pubkey as pubkey, preshared_key, array[host(wnd.wireguard_ip)] as \"allowed_ips!: Vec\" FROM wireguard_network_device wnd JOIN device d ON wnd.device_id = d.id JOIN \"user\" u ON d.user_id = u.id WHERE wireguard_network_id = $1 AND (is_authorized = true OR NOT $2) AND u.is_active = true ORDER BY d.id ASC", + "query": "SELECT d.wireguard_pubkey pubkey, preshared_key, array[host(wnd.wireguard_ip)] \"allowed_ips!: Vec\" FROM wireguard_network_device wnd JOIN device d ON wnd.device_id = d.id JOIN \"user\" u ON d.user_id = u.id WHERE wireguard_network_id = $1 AND (is_authorized = true OR NOT $2) AND u.is_active = true ORDER BY d.id ASC", "describe": { "columns": [ { @@ -31,5 +31,5 @@ null ] }, - "hash": "b773bf99f9e3aafcade8bf57c6f9a49107df5e8d1452796c0bb4f2f657e2ec77" + "hash": "865ffb2d7ee9809eaaac5bb7ad64371f0d066d89e565f72beed5965785dccfd2" } diff --git a/.sqlx/query-8bd853a756c53e3f6bf0ae002c46709a7bb35324df203ce9918d67905d59cdc8.json b/.sqlx/query-8bd853a756c53e3f6bf0ae002c46709a7bb35324df203ce9918d67905d59cdc8.json deleted file mode 100644 index 591494b00b..0000000000 --- a/.sqlx/query-8bd853a756c53e3f6bf0ae002c46709a7bb35324df203ce9918d67905d59cdc8.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE \"settings\" SET \"openid_enabled\" = $2,\"wireguard_enabled\" = $3,\"webhooks_enabled\" = $4,\"worker_enabled\" = $5,\"challenge_template\" = $6,\"instance_name\" = $7,\"main_logo_url\" = $8,\"nav_logo_url\" = $9,\"smtp_server\" = $10,\"smtp_port\" = $11,\"smtp_encryption\" = $12,\"smtp_user\" = $13,\"smtp_password\" = $14,\"smtp_sender\" = $15,\"enrollment_vpn_step_optional\" = $16,\"enrollment_welcome_message\" = $17,\"enrollment_welcome_email\" = $18,\"enrollment_welcome_email_subject\" = $19,\"enrollment_use_welcome_message_as_email\" = $20,\"uuid\" = $21,\"ldap_url\" = $22,\"ldap_bind_username\" = $23,\"ldap_bind_password\" = $24,\"ldap_group_search_base\" = $25,\"ldap_user_search_base\" = $26,\"ldap_user_obj_class\" = $27,\"ldap_group_obj_class\" = $28,\"ldap_username_attr\" = $29,\"ldap_groupname_attr\" = $30,\"ldap_group_member_attr\" = $31,\"ldap_member_attr\" = $32 WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Bool", - "Bool", - "Bool", - "Bool", - "Text", - "Text", - "Text", - "Text", - "Text", - "Int4", - { - "Custom": { - "name": "smtp_encryption", - "kind": { - "Enum": [ - "none", - "starttls", - "implicittls" - ] - } - } - }, - "Text", - "Text", - "Text", - "Bool", - "Text", - "Text", - "Text", - "Bool", - "Uuid", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "8bd853a756c53e3f6bf0ae002c46709a7bb35324df203ce9918d67905d59cdc8" -} diff --git a/.sqlx/query-c9773715f70d267a2bc1e23b86d06c9dd358479e5791b672369d3c0496d51269.json b/.sqlx/query-8bf8ce7230829c1994d0505a947e92e284593d2ba102aecff5569cff0536a616.json similarity index 74% rename from .sqlx/query-c9773715f70d267a2bc1e23b86d06c9dd358479e5791b672369d3c0496d51269.json rename to .sqlx/query-8bf8ce7230829c1994d0505a947e92e284593d2ba102aecff5569cff0536a616.json index 7111120273..128d80763e 100644 --- a/.sqlx/query-c9773715f70d267a2bc1e23b86d06c9dd358479e5791b672369d3c0496d51269.json +++ b/.sqlx/query-8bf8ce7230829c1994d0505a947e92e284593d2ba102aecff5569cff0536a616.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO \"user\" (\"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\",\"recovery_codes\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14) RETURNING id", + "query": "INSERT INTO \"user\" (\"username\",\"password_hash\",\"last_name\",\"first_name\",\"email\",\"phone\",\"mfa_enabled\",\"is_active\",\"openid_sub\",\"totp_enabled\",\"email_mfa_enabled\",\"totp_secret\",\"email_mfa_secret\",\"mfa_method\",\"recovery_codes\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) RETURNING id", "describe": { "columns": [ { @@ -19,6 +19,7 @@ "Text", "Bool", "Bool", + "Text", "Bool", "Bool", "Bytea", @@ -44,5 +45,5 @@ false ] }, - "hash": "c9773715f70d267a2bc1e23b86d06c9dd358479e5791b672369d3c0496d51269" + "hash": "8bf8ce7230829c1994d0505a947e92e284593d2ba102aecff5569cff0536a616" } diff --git a/.sqlx/query-0c5df6263cfa9e2bdf273c51993d40442a7ed65c986eef4e79e9d12aeb32edef.json b/.sqlx/query-8c772071e52ac1fb10931f9f698b7555093606011a31706e23dc891321d8836c.json similarity index 80% rename from .sqlx/query-0c5df6263cfa9e2bdf273c51993d40442a7ed65c986eef4e79e9d12aeb32edef.json rename to .sqlx/query-8c772071e52ac1fb10931f9f698b7555093606011a31706e23dc891321d8836c.json index 3116a4814f..ad29454b29 100644 --- a/.sqlx/query-0c5df6263cfa9e2bdf273c51993d40442a7ed65c986eef4e79e9d12aeb32edef.json +++ b/.sqlx/query-8c772071e52ac1fb10931f9f698b7555093606011a31706e23dc891321d8836c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"mfa_enabled\",\"keepalive_interval\",\"peer_disconnect_threshold\" FROM \"wireguard_network\"", + "query": "SELECT id, \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"mfa_enabled\",\"keepalive_interval\",\"peer_disconnect_threshold\" FROM \"wireguard_network\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -88,5 +88,5 @@ false ] }, - "hash": "0c5df6263cfa9e2bdf273c51993d40442a7ed65c986eef4e79e9d12aeb32edef" + "hash": "8c772071e52ac1fb10931f9f698b7555093606011a31706e23dc891321d8836c" } diff --git a/.sqlx/query-fd950a860fe104136816e1605ed080d70a714e7a02f15e8a1d7b165814cf85d1.json b/.sqlx/query-96b202cd0ffe9bf6d99cd0a02304b2b70ee32801870561190ba787a67068216b.json similarity index 81% rename from .sqlx/query-fd950a860fe104136816e1605ed080d70a714e7a02f15e8a1d7b165814cf85d1.json rename to .sqlx/query-96b202cd0ffe9bf6d99cd0a02304b2b70ee32801870561190ba787a67068216b.json index 07b07f5449..190b99cebd 100644 --- a/.sqlx/query-fd950a860fe104136816e1605ed080d70a714e7a02f15e8a1d7b165814cf85d1.json +++ b/.sqlx/query-96b202cd0ffe9bf6d99cd0a02304b2b70ee32801870561190ba787a67068216b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, mfa_enabled, totp_enabled, email_mfa_enabled, mfa_method as \"mfa_method: MFAMethod\", password_hash, is_active FROM \"user\"", + "query": "SELECT id, mfa_enabled, totp_enabled, email_mfa_enabled, mfa_method \"mfa_method: MFAMethod\", password_hash, is_active, openid_sub FROM \"user\"", "describe": { "columns": [ { @@ -50,6 +50,11 @@ "ordinal": 6, "name": "is_active", "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "openid_sub", + "type_info": "Text" } ], "parameters": { @@ -62,8 +67,9 @@ false, false, true, - false + false, + true ] }, - "hash": "fd950a860fe104136816e1605ed080d70a714e7a02f15e8a1d7b165814cf85d1" + "hash": "96b202cd0ffe9bf6d99cd0a02304b2b70ee32801870561190ba787a67068216b" } diff --git a/.sqlx/query-a0700f9701a61fc64af20165feb4627ef6f053bcb59d81e41dd1cf1bd199b543.json b/.sqlx/query-a0700f9701a61fc64af20165feb4627ef6f053bcb59d81e41dd1cf1bd199b543.json new file mode 100644 index 0000000000..94ec297433 --- /dev/null +++ b/.sqlx/query-a0700f9701a61fc64af20165feb4627ef6f053bcb59d81e41dd1cf1bd199b543.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE openidprovider SET name = $1, base_url = $2, client_id = $3, client_secret = $4 WHERE id = $5", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "a0700f9701a61fc64af20165feb4627ef6f053bcb59d81e41dd1cf1bd199b543" +} diff --git a/.sqlx/query-90eb99cdd712a78d180babe984f73364b510a1a46236ad4b5f80f4d2666b1567.json b/.sqlx/query-a09a0e653c8e7fdd3c8a7a5a9d6bf3e4385adeaff6b1b57f251055f8b47316a6.json similarity index 69% rename from .sqlx/query-90eb99cdd712a78d180babe984f73364b510a1a46236ad4b5f80f4d2666b1567.json rename to .sqlx/query-a09a0e653c8e7fdd3c8a7a5a9d6bf3e4385adeaff6b1b57f251055f8b47316a6.json index 59bdba31bf..82ed536c97 100644 --- a/.sqlx/query-90eb99cdd712a78d180babe984f73364b510a1a46236ad4b5f80f4d2666b1567.json +++ b/.sqlx/query-a09a0e653c8e7fdd3c8a7a5a9d6bf3e4385adeaff6b1b57f251055f8b47316a6.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"oauth2client_id\" FROM \"oauth2authorizedapp\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"oauth2client_id\" FROM \"oauth2authorizedapp\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -30,5 +30,5 @@ false ] }, - "hash": "90eb99cdd712a78d180babe984f73364b510a1a46236ad4b5f80f4d2666b1567" + "hash": "a09a0e653c8e7fdd3c8a7a5a9d6bf3e4385adeaff6b1b57f251055f8b47316a6" } diff --git a/.sqlx/query-a2f21ca8cb0f17686bee01a4fcb14e1be4cddff56ad50f27b2e3d0f42188a6cd.json b/.sqlx/query-a2f21ca8cb0f17686bee01a4fcb14e1be4cddff56ad50f27b2e3d0f42188a6cd.json new file mode 100644 index 0000000000..eec1c651ba --- /dev/null +++ b/.sqlx/query-a2f21ca8cb0f17686bee01a4fcb14e1be4cddff56ad50f27b2e3d0f42188a6cd.json @@ -0,0 +1,57 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"settings\" SET openid_enabled = $1, wireguard_enabled = $2, webhooks_enabled = $3, worker_enabled = $4, challenge_template = $5, instance_name = $6, main_logo_url = $7, nav_logo_url = $8, smtp_server = $9, smtp_port = $10, smtp_encryption = $11, smtp_user = $12, smtp_password = $13, smtp_sender = $14, enrollment_vpn_step_optional = $15, enrollment_welcome_message = $16, enrollment_welcome_email = $17, enrollment_welcome_email_subject = $18, enrollment_use_welcome_message_as_email = $19, uuid = $20, ldap_url = $21, ldap_bind_username = $22, ldap_bind_password = $23, ldap_group_search_base = $24, ldap_user_search_base = $25, ldap_user_obj_class = $26, ldap_group_obj_class = $27, ldap_username_attr = $28, ldap_groupname_attr = $29, ldap_group_member_attr = $30, ldap_member_attr = $31, openid_create_account = $32, license = $33 WHERE id = 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bool", + "Bool", + "Bool", + "Bool", + "Text", + "Text", + "Text", + "Text", + "Text", + "Int4", + { + "Custom": { + "name": "smtp_encryption", + "kind": { + "Enum": [ + "none", + "starttls", + "implicittls" + ] + } + } + }, + "Text", + "Text", + "Text", + "Bool", + "Text", + "Text", + "Text", + "Bool", + "Uuid", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Bool", + "Text" + ] + }, + "nullable": [] + }, + "hash": "a2f21ca8cb0f17686bee01a4fcb14e1be4cddff56ad50f27b2e3d0f42188a6cd" +} diff --git a/.sqlx/query-a6fe220739875c6894e1a678d4c24de34b7c3ca8bd5e48ed9f313d20cbe8f8e4.json b/.sqlx/query-a6fe220739875c6894e1a678d4c24de34b7c3ca8bd5e48ed9f313d20cbe8f8e4.json new file mode 100644 index 0000000000..e32f0cf87a --- /dev/null +++ b/.sqlx/query-a6fe220739875c6894e1a678d4c24de34b7c3ca8bd5e48ed9f313d20cbe8f8e4.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO \"openidprovider\" (\"name\",\"base_url\",\"client_id\",\"client_secret\") VALUES ($1,$2,$3,$4) RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "a6fe220739875c6894e1a678d4c24de34b7c3ca8bd5e48ed9f313d20cbe8f8e4" +} diff --git a/.sqlx/query-295046c6b12e917661abebd70d047d23b9102682c25541a0cb51080689599405.json b/.sqlx/query-a74e2fda629b2b2916e3f034df84470cf7fb21a2f066559fad80a3d2396f9e21.json similarity index 79% rename from .sqlx/query-295046c6b12e917661abebd70d047d23b9102682c25541a0cb51080689599405.json rename to .sqlx/query-a74e2fda629b2b2916e3f034df84470cf7fb21a2f066559fad80a3d2396f9e21.json index bbbacd1311..6bc42a2bc4 100644 --- a/.sqlx/query-295046c6b12e917661abebd70d047d23b9102682c25541a0cb51080689599405.json +++ b/.sqlx/query-a74e2fda629b2b2916e3f034df84470cf7fb21a2f066559fad80a3d2396f9e21.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE device_id = $1 AND wireguard_network_id = $2", + "query": "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE device_id = $1 AND wireguard_network_id = $2", "describe": { "columns": [ { @@ -49,5 +49,5 @@ true ] }, - "hash": "295046c6b12e917661abebd70d047d23b9102682c25541a0cb51080689599405" + "hash": "a74e2fda629b2b2916e3f034df84470cf7fb21a2f066559fad80a3d2396f9e21" } diff --git a/.sqlx/query-6ed1d008a99fa449325cb3d6d4b0231304b857b23460f0ca2f74e0d17f8dbc77.json b/.sqlx/query-a944946c95a75f4bc7d0a96c802886816f2aef26cc0f349426228f292c110bfd.json similarity index 74% rename from .sqlx/query-6ed1d008a99fa449325cb3d6d4b0231304b857b23460f0ca2f74e0d17f8dbc77.json rename to .sqlx/query-a944946c95a75f4bc7d0a96c802886816f2aef26cc0f349426228f292c110bfd.json index 55a127b3c3..a2e2f887b6 100644 --- a/.sqlx/query-6ed1d008a99fa449325cb3d6d4b0231304b857b23460f0ca2f74e0d17f8dbc77.json +++ b/.sqlx/query-a944946c95a75f4bc7d0a96c802886816f2aef26cc0f349426228f292c110bfd.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"serial\",\"user_id\" FROM \"yubikey\"", + "query": "SELECT id, \"name\",\"serial\",\"user_id\" FROM \"yubikey\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -34,5 +34,5 @@ false ] }, - "hash": "6ed1d008a99fa449325cb3d6d4b0231304b857b23460f0ca2f74e0d17f8dbc77" + "hash": "a944946c95a75f4bc7d0a96c802886816f2aef26cc0f349426228f292c110bfd" } diff --git a/.sqlx/query-33a1e2f1904757c775d389fa99d67916b7b220d6aa1fe8bb6690f85ed1cd5666.json b/.sqlx/query-ab42d3c8bd9969e10eb02c4eccf176198608932ae8b0238ef10afb2fb548f25b.json similarity index 71% rename from .sqlx/query-33a1e2f1904757c775d389fa99d67916b7b220d6aa1fe8bb6690f85ed1cd5666.json rename to .sqlx/query-ab42d3c8bd9969e10eb02c4eccf176198608932ae8b0238ef10afb2fb548f25b.json index 462ab0695d..387bd41534 100644 --- a/.sqlx/query-33a1e2f1904757c775d389fa99d67916b7b220d6aa1fe8bb6690f85ed1cd5666.json +++ b/.sqlx/query-ab42d3c8bd9969e10eb02c4eccf176198608932ae8b0238ef10afb2fb548f25b.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE \"user\".username = $1", + "query": "SELECT device.id, name, wireguard_pubkey, user_id, created FROM device JOIN \"user\" ON device.user_id = \"user\".id WHERE \"user\".username = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "33a1e2f1904757c775d389fa99d67916b7b220d6aa1fe8bb6690f85ed1cd5666" + "hash": "ab42d3c8bd9969e10eb02c4eccf176198608932ae8b0238ef10afb2fb548f25b" } diff --git a/.sqlx/query-ace6a5da2485d065f1cd6c8d66ec5ee98d9d5e238fbce7d91c899163d0311234.json b/.sqlx/query-ace6a5da2485d065f1cd6c8d66ec5ee98d9d5e238fbce7d91c899163d0311234.json new file mode 100644 index 0000000000..b5eae9a2d5 --- /dev/null +++ b/.sqlx/query-ace6a5da2485d065f1cd6c8d66ec5ee98d9d5e238fbce7d91c899163d0311234.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM pollingtoken WHERE device_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [] + }, + "hash": "ace6a5da2485d065f1cd6c8d66ec5ee98d9d5e238fbce7d91c899163d0311234" +} diff --git a/.sqlx/query-52c659862cef5cd45bbe7bdc58cf70c93c46740cc778825583e060cdee73a212.json b/.sqlx/query-b0d21b63dc414e3738a85e9e5ed8a71cbb3019f86f5d69e186efba7f0fabd4d1.json similarity index 80% rename from .sqlx/query-52c659862cef5cd45bbe7bdc58cf70c93c46740cc778825583e060cdee73a212.json rename to .sqlx/query-b0d21b63dc414e3738a85e9e5ed8a71cbb3019f86f5d69e186efba7f0fabd4d1.json index 72a1d6e0f5..84f36c6df2 100644 --- a/.sqlx/query-52c659862cef5cd45bbe7bdc58cf70c93c46740cc778825583e060cdee73a212.json +++ b/.sqlx/query-b0d21b63dc414e3738a85e9e5ed8a71cbb3019f86f5d69e186efba7f0fabd4d1.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT \"user\".id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active FROM \"user\" JOIN group_user ON \"user\".id = group_user.user_id WHERE group_user.group_id = $1", + "query": "SELECT id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE openid_sub = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -50,13 +50,13 @@ }, { "ordinal": 9, - "name": "totp_secret", - "type_info": "Bytea" + "name": "email_mfa_enabled", + "type_info": "Bool" }, { "ordinal": 10, - "name": "email_mfa_enabled", - "type_info": "Bool" + "name": "totp_secret", + "type_info": "Bytea" }, { "ordinal": 11, @@ -90,11 +90,16 @@ "ordinal": 14, "name": "is_active", "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "openid_sub", + "type_info": "Text" } ], "parameters": { "Left": [ - "Int8" + "Text" ] }, "nullable": [ @@ -107,13 +112,14 @@ true, false, false, - true, false, true, + true, + false, false, false, - false + true ] }, - "hash": "52c659862cef5cd45bbe7bdc58cf70c93c46740cc778825583e060cdee73a212" + "hash": "b0d21b63dc414e3738a85e9e5ed8a71cbb3019f86f5d69e186efba7f0fabd4d1" } diff --git a/.sqlx/query-b7d7474762519f378dcb1e9ebd244f0704634b387855fac359c7fd407eda4b44.json b/.sqlx/query-b7d7474762519f378dcb1e9ebd244f0704634b387855fac359c7fd407eda4b44.json new file mode 100644 index 0000000000..dce9b16b27 --- /dev/null +++ b/.sqlx/query-b7d7474762519f378dcb1e9ebd244f0704634b387855fac359c7fd407eda4b44.json @@ -0,0 +1,125 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT u.id, u.username, u.password_hash, u.last_name, u.first_name, u.email, u.phone, u.mfa_enabled, u.totp_enabled, u.email_mfa_enabled, u.totp_secret, u.email_mfa_secret, u.mfa_method \"mfa_method: _\", u.recovery_codes, u.is_active, u.openid_sub FROM \"user\" u JOIN \"device\" d ON u.id = d.user_id WHERE d.id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "password_hash", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "last_name", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "first_name", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "phone", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "mfa_enabled", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "totp_enabled", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "email_mfa_enabled", + "type_info": "Bool" + }, + { + "ordinal": 10, + "name": "totp_secret", + "type_info": "Bytea" + }, + { + "ordinal": 11, + "name": "email_mfa_secret", + "type_info": "Bytea" + }, + { + "ordinal": 12, + "name": "mfa_method: _", + "type_info": { + "Custom": { + "name": "mfa_method", + "kind": { + "Enum": [ + "none", + "one_time_password", + "webauthn", + "web3", + "email" + ] + } + } + } + }, + { + "ordinal": 13, + "name": "recovery_codes", + "type_info": "TextArray" + }, + { + "ordinal": 14, + "name": "is_active", + "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "openid_sub", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + true, + false, + false, + false, + true, + true, + false, + false, + false, + true + ] + }, + "hash": "b7d7474762519f378dcb1e9ebd244f0704634b387855fac359c7fd407eda4b44" +} diff --git a/.sqlx/query-20dda55cfcf38db23d5553b65a9e4f48275d7b1d588869791a6741fa11be38a5.json b/.sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json similarity index 73% rename from .sqlx/query-20dda55cfcf38db23d5553b65a9e4f48275d7b1d588869791a6741fa11be38a5.json rename to .sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json index 4b6e6f131e..b7f40eaee9 100644 --- a/.sqlx/query-20dda55cfcf38db23d5553b65a9e4f48275d7b1d588869791a6741fa11be38a5.json +++ b/.sqlx/query-b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", endpoint, upload \"upload!\", download \"download!\", latest_handshake \"latest_handshake!\", allowed_ips FROM wireguard_peer_stats WHERE device_id = $1 AND network = $2 ORDER BY collected_at DESC LIMIT 1", + "query": "SELECT id, device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", endpoint, upload \"upload!\", download \"download!\", latest_handshake \"latest_handshake!\", allowed_ips FROM wireguard_peer_stats WHERE device_id = $1 AND network = $2 ORDER BY collected_at DESC LIMIT 1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -67,5 +67,5 @@ true ] }, - "hash": "20dda55cfcf38db23d5553b65a9e4f48275d7b1d588869791a6741fa11be38a5" + "hash": "b83158ecab3024ae6e63c2f59e36c94b531af794d9540d1585eb5512db787a38" } diff --git a/.sqlx/query-e1a3ec06d90dc8e09f00a26f1f3d272e338aa169785058f71db480cc511bc658.json b/.sqlx/query-b915edbe87546e273c000d9494442a5b65504dd01c4e7271cdd5ec6dcf2b4c36.json similarity index 78% rename from .sqlx/query-e1a3ec06d90dc8e09f00a26f1f3d272e338aa169785058f71db480cc511bc658.json rename to .sqlx/query-b915edbe87546e273c000d9494442a5b65504dd01c4e7271cdd5ec6dcf2b4c36.json index 9047e30d9a..e77aaa5f96 100644 --- a/.sqlx/query-e1a3ec06d90dc8e09f00a26f1f3d272e338aa169785058f71db480cc511bc658.json +++ b/.sqlx/query-b915edbe87546e273c000d9494442a5b65504dd01c4e7271cdd5ec6dcf2b4c36.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT * FROM \"yubikey\" WHERE user_id = $1", + "query": "SELECT id, name, serial, user_id FROM \"yubikey\" WHERE user_id = $1", "describe": { "columns": [ { @@ -10,12 +10,12 @@ }, { "ordinal": 1, - "name": "serial", + "name": "name", "type_info": "Text" }, { "ordinal": 2, - "name": "name", + "name": "serial", "type_info": "Text" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "e1a3ec06d90dc8e09f00a26f1f3d272e338aa169785058f71db480cc511bc658" + "hash": "b915edbe87546e273c000d9494442a5b65504dd01c4e7271cdd5ec6dcf2b4c36" } diff --git a/.sqlx/query-b33fa60772e858280c021b3774d01273453d13af4321cae2d244748ca4254b29.json b/.sqlx/query-ba65214847bd66c0b57d1d2eea65d862c062b3224e61598247ec8c927216caf3.json similarity index 67% rename from .sqlx/query-b33fa60772e858280c021b3774d01273453d13af4321cae2d244748ca4254b29.json rename to .sqlx/query-ba65214847bd66c0b57d1d2eea65d862c062b3224e61598247ec8c927216caf3.json index 89e360d768..0f206c3b54 100644 --- a/.sqlx/query-b33fa60772e858280c021b3774d01273453d13af4321cae2d244748ca4254b29.json +++ b/.sqlx/query-ba65214847bd66c0b57d1d2eea65d862c062b3224e61598247ec8c927216caf3.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", name FROM \"group\" WHERE name = $1", + "query": "SELECT id, name FROM \"group\" WHERE name = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -24,5 +24,5 @@ false ] }, - "hash": "b33fa60772e858280c021b3774d01273453d13af4321cae2d244748ca4254b29" + "hash": "ba65214847bd66c0b57d1d2eea65d862c062b3224e61598247ec8c927216caf3" } diff --git a/.sqlx/query-e253723a52a1632fd190ad5404cc0a6dfaf336b5416dafb8d634b521d3dce8d3.json b/.sqlx/query-bc5b07156c76b4d68f12dbf0b3a4d9108883708bcca7657a08f03abc31047026.json similarity index 78% rename from .sqlx/query-e253723a52a1632fd190ad5404cc0a6dfaf336b5416dafb8d634b521d3dce8d3.json rename to .sqlx/query-bc5b07156c76b4d68f12dbf0b3a4d9108883708bcca7657a08f03abc31047026.json index 98488e7959..4b80445b14 100644 --- a/.sqlx/query-e253723a52a1632fd190ad5404cc0a6dfaf336b5416dafb8d634b521d3dce8d3.json +++ b/.sqlx/query-bc5b07156c76b4d68f12dbf0b3a4d9108883708bcca7657a08f03abc31047026.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT \"user\".id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active FROM \"user\"\n INNER JOIN \"group_user\" ON \"user\".id = \"group_user\".user_id\n INNER JOIN \"group\" ON \"group_user\".group_id = \"group\".id\n WHERE \"group\".name = $1", + "query": "SELECT \"user\".id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" JOIN group_user ON \"user\".id = group_user.user_id WHERE group_user.group_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -90,11 +90,16 @@ "ordinal": 14, "name": "is_active", "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "openid_sub", + "type_info": "Text" } ], "parameters": { "Left": [ - "Text" + "Int8" ] }, "nullable": [ @@ -112,8 +117,9 @@ true, false, false, - false + false, + true ] }, - "hash": "e253723a52a1632fd190ad5404cc0a6dfaf336b5416dafb8d634b521d3dce8d3" + "hash": "bc5b07156c76b4d68f12dbf0b3a4d9108883708bcca7657a08f03abc31047026" } diff --git a/.sqlx/query-e77aec5f5c3e42b65ded303f5360c22bb3f882ae1d82b2f72ea5ff933a407d91.json b/.sqlx/query-bfeeea313651b1ca70eff19f6743904e2a39a5277f079c17ba90f6bdc20c3087.json similarity index 74% rename from .sqlx/query-e77aec5f5c3e42b65ded303f5360c22bb3f882ae1d82b2f72ea5ff933a407d91.json rename to .sqlx/query-bfeeea313651b1ca70eff19f6743904e2a39a5277f079c17ba90f6bdc20c3087.json index d7eac8f7bf..0344215a92 100644 --- a/.sqlx/query-e77aec5f5c3e42b65ded303f5360c22bb3f882ae1d82b2f72ea5ff933a407d91.json +++ b/.sqlx/query-bfeeea313651b1ca70eff19f6743904e2a39a5277f079c17ba90f6bdc20c3087.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"serial\",\"user_id\" FROM \"yubikey\" WHERE id = $1", + "query": "SELECT id, \"name\",\"serial\",\"user_id\" FROM \"yubikey\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -36,5 +36,5 @@ false ] }, - "hash": "e77aec5f5c3e42b65ded303f5360c22bb3f882ae1d82b2f72ea5ff933a407d91" + "hash": "bfeeea313651b1ca70eff19f6743904e2a39a5277f079c17ba90f6bdc20c3087" } diff --git a/.sqlx/query-e1875cb32cf6f270541b27319cb472abd8f6ed0dccc77a2ba0908dd487975ef5.json b/.sqlx/query-c0050b334d9efcceb0cea4e9a04dda908d74c05e9b08389215dbcababde5d7fb.json similarity index 79% rename from .sqlx/query-e1875cb32cf6f270541b27319cb472abd8f6ed0dccc77a2ba0908dd487975ef5.json rename to .sqlx/query-c0050b334d9efcceb0cea4e9a04dda908d74c05e9b08389215dbcababde5d7fb.json index bd60bb9df9..1b65d2d312 100644 --- a/.sqlx/query-e1875cb32cf6f270541b27319cb472abd8f6ed0dccc77a2ba0908dd487975ef5.json +++ b/.sqlx/query-c0050b334d9efcceb0cea4e9a04dda908d74c05e9b08389215dbcababde5d7fb.json @@ -1,65 +1,60 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"openid_enabled\",\"wireguard_enabled\",\"webhooks_enabled\",\"worker_enabled\",\"challenge_template\",\"instance_name\",\"main_logo_url\",\"nav_logo_url\",\"smtp_server\",\"smtp_port\",\"smtp_encryption\" \"smtp_encryption: _\",\"smtp_user\",\"smtp_password\" \"smtp_password?: SecretString\",\"smtp_sender\",\"enrollment_vpn_step_optional\",\"enrollment_welcome_message\",\"enrollment_welcome_email\",\"enrollment_welcome_email_subject\",\"enrollment_use_welcome_message_as_email\",\"uuid\",\"ldap_url\",\"ldap_bind_username\",\"ldap_bind_password\" \"ldap_bind_password?: SecretString\",\"ldap_group_search_base\",\"ldap_user_search_base\",\"ldap_user_obj_class\",\"ldap_group_obj_class\",\"ldap_username_attr\",\"ldap_groupname_attr\",\"ldap_group_member_attr\",\"ldap_member_attr\" FROM \"settings\"", + "query": "SELECT openid_enabled, wireguard_enabled, webhooks_enabled, worker_enabled, challenge_template, instance_name, main_logo_url, nav_logo_url, smtp_server, smtp_port, smtp_encryption \"smtp_encryption: _\", smtp_user, smtp_password \"smtp_password?: SecretString\", smtp_sender, enrollment_vpn_step_optional, enrollment_welcome_message, enrollment_welcome_email, enrollment_welcome_email_subject, enrollment_use_welcome_message_as_email, uuid, ldap_url, ldap_bind_username, ldap_bind_password \"ldap_bind_password?: SecretString\", ldap_group_search_base, ldap_user_search_base, ldap_user_obj_class, ldap_group_obj_class, ldap_username_attr, ldap_groupname_attr, ldap_group_member_attr, ldap_member_attr, openid_create_account, license FROM \"settings\" WHERE id = 1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, "name": "openid_enabled", "type_info": "Bool" }, { - "ordinal": 2, + "ordinal": 1, "name": "wireguard_enabled", "type_info": "Bool" }, { - "ordinal": 3, + "ordinal": 2, "name": "webhooks_enabled", "type_info": "Bool" }, { - "ordinal": 4, + "ordinal": 3, "name": "worker_enabled", "type_info": "Bool" }, { - "ordinal": 5, + "ordinal": 4, "name": "challenge_template", "type_info": "Text" }, { - "ordinal": 6, + "ordinal": 5, "name": "instance_name", "type_info": "Text" }, { - "ordinal": 7, + "ordinal": 6, "name": "main_logo_url", "type_info": "Text" }, { - "ordinal": 8, + "ordinal": 7, "name": "nav_logo_url", "type_info": "Text" }, { - "ordinal": 9, + "ordinal": 8, "name": "smtp_server", "type_info": "Text" }, { - "ordinal": 10, + "ordinal": 9, "name": "smtp_port", "type_info": "Int4" }, { - "ordinal": 11, + "ordinal": 10, "name": "smtp_encryption: _", "type_info": { "Custom": { @@ -75,104 +70,114 @@ } }, { - "ordinal": 12, + "ordinal": 11, "name": "smtp_user", "type_info": "Text" }, { - "ordinal": 13, + "ordinal": 12, "name": "smtp_password?: SecretString", "type_info": "Text" }, { - "ordinal": 14, + "ordinal": 13, "name": "smtp_sender", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 14, "name": "enrollment_vpn_step_optional", "type_info": "Bool" }, { - "ordinal": 16, + "ordinal": 15, "name": "enrollment_welcome_message", "type_info": "Text" }, { - "ordinal": 17, + "ordinal": 16, "name": "enrollment_welcome_email", "type_info": "Text" }, { - "ordinal": 18, + "ordinal": 17, "name": "enrollment_welcome_email_subject", "type_info": "Text" }, { - "ordinal": 19, + "ordinal": 18, "name": "enrollment_use_welcome_message_as_email", "type_info": "Bool" }, { - "ordinal": 20, + "ordinal": 19, "name": "uuid", "type_info": "Uuid" }, { - "ordinal": 21, + "ordinal": 20, "name": "ldap_url", "type_info": "Text" }, { - "ordinal": 22, + "ordinal": 21, "name": "ldap_bind_username", "type_info": "Text" }, { - "ordinal": 23, + "ordinal": 22, "name": "ldap_bind_password?: SecretString", "type_info": "Text" }, { - "ordinal": 24, + "ordinal": 23, "name": "ldap_group_search_base", "type_info": "Text" }, { - "ordinal": 25, + "ordinal": 24, "name": "ldap_user_search_base", "type_info": "Text" }, { - "ordinal": 26, + "ordinal": 25, "name": "ldap_user_obj_class", "type_info": "Text" }, { - "ordinal": 27, + "ordinal": 26, "name": "ldap_group_obj_class", "type_info": "Text" }, { - "ordinal": 28, + "ordinal": 27, "name": "ldap_username_attr", "type_info": "Text" }, { - "ordinal": 29, + "ordinal": 28, "name": "ldap_groupname_attr", "type_info": "Text" }, { - "ordinal": 30, + "ordinal": 29, "name": "ldap_group_member_attr", "type_info": "Text" }, { - "ordinal": 31, + "ordinal": 30, "name": "ldap_member_attr", "type_info": "Text" + }, + { + "ordinal": 31, + "name": "openid_create_account", + "type_info": "Bool" + }, + { + "ordinal": 32, + "name": "license", + "type_info": "Text" } ], "parameters": { @@ -187,7 +192,6 @@ false, false, false, - false, true, true, false, @@ -210,8 +214,10 @@ true, true, true, + true, + false, true ] }, - "hash": "e1875cb32cf6f270541b27319cb472abd8f6ed0dccc77a2ba0908dd487975ef5" + "hash": "c0050b334d9efcceb0cea4e9a04dda908d74c05e9b08389215dbcababde5d7fb" } diff --git a/.sqlx/query-9b443a223ba94d9a696a0d8ebe188e50e49f404f8b0057a3015c067c5f57c1bf.json b/.sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json similarity index 79% rename from .sqlx/query-9b443a223ba94d9a696a0d8ebe188e50e49f404f8b0057a3015c067c5f57c1bf.json rename to .sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json index e45b1c9059..9a93f3484a 100644 --- a/.sqlx/query-9b443a223ba94d9a696a0d8ebe188e50e49f404f8b0057a3015c067c5f57c1bf.json +++ b/.sqlx/query-c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\" WHERE id = $1", + "query": "SELECT id, \"user_id\",\"address\",\"name\",\"chain_id\",\"challenge_message\",\"challenge_signature\",\"creation_timestamp\",\"validation_timestamp\",\"use_for_mfa\" FROM \"wallet\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -72,5 +72,5 @@ false ] }, - "hash": "9b443a223ba94d9a696a0d8ebe188e50e49f404f8b0057a3015c067c5f57c1bf" + "hash": "c4aae1903dfd0c63be33d58794f805c685a6d61c231dcbb4ce92fb3333a41a0d" } diff --git a/.sqlx/query-ca74174177efd38a84835e809b91a7b39b9389ae01437b7d9405ffa074388279.json b/.sqlx/query-ca74174177efd38a84835e809b91a7b39b9389ae01437b7d9405ffa074388279.json new file mode 100644 index 0000000000..bc0e41c842 --- /dev/null +++ b/.sqlx/query-ca74174177efd38a84835e809b91a7b39b9389ae01437b7d9405ffa074388279.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, \"name\",\"base_url\",\"client_id\",\"client_secret\" FROM \"openidprovider\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "base_url", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "client_id", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "client_secret", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "ca74174177efd38a84835e809b91a7b39b9389ae01437b7d9405ffa074388279" +} diff --git a/.sqlx/query-52b93ff07493589383745923dbe340b5adde936a4e4474d530b70002eb92063e.json b/.sqlx/query-cb09994909c0e17b4c1740c11878c1649c552173dbc94b1f0fadca7af4088a06.json similarity index 79% rename from .sqlx/query-52b93ff07493589383745923dbe340b5adde936a4e4474d530b70002eb92063e.json rename to .sqlx/query-cb09994909c0e17b4c1740c11878c1649c552173dbc94b1f0fadca7af4088a06.json index 50c5d3c5f7..e3a06dea58 100644 --- a/.sqlx/query-52b93ff07493589383745923dbe340b5adde936a4e4474d530b70002eb92063e.json +++ b/.sqlx/query-cb09994909c0e17b4c1740c11878c1649c552173dbc94b1f0fadca7af4088a06.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"url\",\"description\",\"token\",\"enabled\",\"on_user_created\",\"on_user_deleted\",\"on_user_modified\",\"on_hwkey_provision\" FROM \"webhook\"", + "query": "SELECT id, \"url\",\"description\",\"token\",\"enabled\",\"on_user_created\",\"on_user_deleted\",\"on_user_modified\",\"on_hwkey_provision\" FROM \"webhook\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -64,5 +64,5 @@ false ] }, - "hash": "52b93ff07493589383745923dbe340b5adde936a4e4474d530b70002eb92063e" + "hash": "cb09994909c0e17b4c1740c11878c1649c552173dbc94b1f0fadca7af4088a06" } diff --git a/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json b/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json new file mode 100644 index 0000000000..787a75d7d9 --- /dev/null +++ b/.sqlx/query-ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE \"enterprisesettings\" SET admin_device_management = $1, disable_all_traffic = $2, only_client_activation = $3 WHERE id = 1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bool", + "Bool", + "Bool" + ] + }, + "nullable": [] + }, + "hash": "ccd62ea7526078c9db47812e7f6a5e7829eae217ad9f7f3b0b03aa02f8808dc2" +} diff --git a/.sqlx/query-1a29720d6c1efc4460c6bc10b96ab42b6daf888de145d62fab1739ebff759b9b.json b/.sqlx/query-d5167701c8ca8437fa35810062288a2dc1550d4d4e750d846ddb6090f65566ac.json similarity index 77% rename from .sqlx/query-1a29720d6c1efc4460c6bc10b96ab42b6daf888de145d62fab1739ebff759b9b.json rename to .sqlx/query-d5167701c8ca8437fa35810062288a2dc1550d4d4e750d846ddb6090f65566ac.json index ed787b82bb..7107a8b59d 100644 --- a/.sqlx/query-1a29720d6c1efc4460c6bc10b96ab42b6daf888de145d62fab1739ebff759b9b.json +++ b/.sqlx/query-d5167701c8ca8437fa35810062288a2dc1550d4d4e750d846ddb6090f65566ac.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT k.id as key_id, k.name, k.key_type \"key_type: AuthenticationKeyType\", k.key, k.user_id, k.yubikey_id, y.name \"yubikey_name: Option\", y.serial \"serial: Option\" FROM \"authentication_key\" k LEFT JOIN \"yubikey\" y ON k.yubikey_id = y.id WHERE k.user_id = $1", + "query": "SELECT k.id key_id, k.name, k.key_type \"key_type: AuthenticationKeyType\", k.key, k.user_id, k.yubikey_id, y.name \"yubikey_name: Option\", y.serial \"serial: Option\" FROM \"authentication_key\" k LEFT JOIN \"yubikey\" y ON k.yubikey_id = y.id WHERE k.user_id = $1", "describe": { "columns": [ { @@ -70,5 +70,5 @@ false ] }, - "hash": "1a29720d6c1efc4460c6bc10b96ab42b6daf888de145d62fab1739ebff759b9b" + "hash": "d5167701c8ca8437fa35810062288a2dc1550d4d4e750d846ddb6090f65566ac" } diff --git a/.sqlx/query-dafe0c3d80ed8e09771cf910d6a7696bb16daaece311438358321c8e8ea3b65f.json b/.sqlx/query-dafe0c3d80ed8e09771cf910d6a7696bb16daaece311438358321c8e8ea3b65f.json new file mode 100644 index 0000000000..4619296423 --- /dev/null +++ b/.sqlx/query-dafe0c3d80ed8e09771cf910d6a7696bb16daaece311438358321c8e8ea3b65f.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, \"name\",\"base_url\",\"client_id\",\"client_secret\" FROM \"openidprovider\" WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "base_url", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "client_id", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "client_secret", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "dafe0c3d80ed8e09771cf910d6a7696bb16daaece311438358321c8e8ea3b65f" +} diff --git a/.sqlx/query-6b659b309306098a1f8b5b5592facba9cd54f93f5239fbc62a858bb91f502472.json b/.sqlx/query-dc4bc55d768a79bbb01f61b124578fbfe4b2c10709d6d176d4ac622854318d8c.json similarity index 76% rename from .sqlx/query-6b659b309306098a1f8b5b5592facba9cd54f93f5239fbc62a858bb91f502472.json rename to .sqlx/query-dc4bc55d768a79bbb01f61b124578fbfe4b2c10709d6d176d4ac622854318d8c.json index 0bbbc964f6..1b2cf0aaf9 100644 --- a/.sqlx/query-6b659b309306098a1f8b5b5592facba9cd54f93f5239fbc62a858bb91f502472.json +++ b/.sqlx/query-dc4bc55d768a79bbb01f61b124578fbfe4b2c10709d6d176d4ac622854318d8c.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"client_id\",\"client_secret\",\"redirect_uri\" \"redirect_uri: _\",\"scope\" \"scope: _\",\"name\",\"enabled\" FROM \"oauth2client\"", + "query": "SELECT id, \"client_id\",\"client_secret\",\"redirect_uri\" \"redirect_uri: _\",\"scope\" \"scope: _\",\"name\",\"enabled\" FROM \"oauth2client\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -52,5 +52,5 @@ false ] }, - "hash": "6b659b309306098a1f8b5b5592facba9cd54f93f5239fbc62a858bb91f502472" + "hash": "dc4bc55d768a79bbb01f61b124578fbfe4b2c10709d6d176d4ac622854318d8c" } diff --git a/.sqlx/query-dea2f3d8b9508ef1df84e204816a9cdc53103547d3d273803313bc091c72c323.json b/.sqlx/query-dea2f3d8b9508ef1df84e204816a9cdc53103547d3d273803313bc091c72c323.json new file mode 100644 index 0000000000..83298bd6bc --- /dev/null +++ b/.sqlx/query-dea2f3d8b9508ef1df84e204816a9cdc53103547d3d273803313bc091c72c323.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, name, base_url, client_id, client_secret FROM openidprovider", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "base_url", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "client_id", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "client_secret", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "dea2f3d8b9508ef1df84e204816a9cdc53103547d3d273803313bc091c72c323" +} diff --git a/.sqlx/query-e181b36a398dc70efd14bb7dc52138a17a5ec4c3210d5960149684e77ae88726.json b/.sqlx/query-e181b36a398dc70efd14bb7dc52138a17a5ec4c3210d5960149684e77ae88726.json deleted file mode 100644 index 45a90b155d..0000000000 --- a/.sqlx/query-e181b36a398dc70efd14bb7dc52138a17a5ec4c3210d5960149684e77ae88726.json +++ /dev/null @@ -1,219 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"openid_enabled\",\"wireguard_enabled\",\"webhooks_enabled\",\"worker_enabled\",\"challenge_template\",\"instance_name\",\"main_logo_url\",\"nav_logo_url\",\"smtp_server\",\"smtp_port\",\"smtp_encryption\" \"smtp_encryption: _\",\"smtp_user\",\"smtp_password\" \"smtp_password?: SecretString\",\"smtp_sender\",\"enrollment_vpn_step_optional\",\"enrollment_welcome_message\",\"enrollment_welcome_email\",\"enrollment_welcome_email_subject\",\"enrollment_use_welcome_message_as_email\",\"uuid\",\"ldap_url\",\"ldap_bind_username\",\"ldap_bind_password\" \"ldap_bind_password?: SecretString\",\"ldap_group_search_base\",\"ldap_user_search_base\",\"ldap_user_obj_class\",\"ldap_group_obj_class\",\"ldap_username_attr\",\"ldap_groupname_attr\",\"ldap_group_member_attr\",\"ldap_member_attr\" FROM \"settings\" WHERE id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id?", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "openid_enabled", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "wireguard_enabled", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "webhooks_enabled", - "type_info": "Bool" - }, - { - "ordinal": 4, - "name": "worker_enabled", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "challenge_template", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "instance_name", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "main_logo_url", - "type_info": "Text" - }, - { - "ordinal": 8, - "name": "nav_logo_url", - "type_info": "Text" - }, - { - "ordinal": 9, - "name": "smtp_server", - "type_info": "Text" - }, - { - "ordinal": 10, - "name": "smtp_port", - "type_info": "Int4" - }, - { - "ordinal": 11, - "name": "smtp_encryption: _", - "type_info": { - "Custom": { - "name": "smtp_encryption", - "kind": { - "Enum": [ - "none", - "starttls", - "implicittls" - ] - } - } - } - }, - { - "ordinal": 12, - "name": "smtp_user", - "type_info": "Text" - }, - { - "ordinal": 13, - "name": "smtp_password?: SecretString", - "type_info": "Text" - }, - { - "ordinal": 14, - "name": "smtp_sender", - "type_info": "Text" - }, - { - "ordinal": 15, - "name": "enrollment_vpn_step_optional", - "type_info": "Bool" - }, - { - "ordinal": 16, - "name": "enrollment_welcome_message", - "type_info": "Text" - }, - { - "ordinal": 17, - "name": "enrollment_welcome_email", - "type_info": "Text" - }, - { - "ordinal": 18, - "name": "enrollment_welcome_email_subject", - "type_info": "Text" - }, - { - "ordinal": 19, - "name": "enrollment_use_welcome_message_as_email", - "type_info": "Bool" - }, - { - "ordinal": 20, - "name": "uuid", - "type_info": "Uuid" - }, - { - "ordinal": 21, - "name": "ldap_url", - "type_info": "Text" - }, - { - "ordinal": 22, - "name": "ldap_bind_username", - "type_info": "Text" - }, - { - "ordinal": 23, - "name": "ldap_bind_password?: SecretString", - "type_info": "Text" - }, - { - "ordinal": 24, - "name": "ldap_group_search_base", - "type_info": "Text" - }, - { - "ordinal": 25, - "name": "ldap_user_search_base", - "type_info": "Text" - }, - { - "ordinal": 26, - "name": "ldap_user_obj_class", - "type_info": "Text" - }, - { - "ordinal": 27, - "name": "ldap_group_obj_class", - "type_info": "Text" - }, - { - "ordinal": 28, - "name": "ldap_username_attr", - "type_info": "Text" - }, - { - "ordinal": 29, - "name": "ldap_groupname_attr", - "type_info": "Text" - }, - { - "ordinal": 30, - "name": "ldap_group_member_attr", - "type_info": "Text" - }, - { - "ordinal": 31, - "name": "ldap_member_attr", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - true, - true, - false, - true, - true, - true, - false, - true, - true, - true, - false, - false, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true - ] - }, - "hash": "e181b36a398dc70efd14bb7dc52138a17a5ec4c3210d5960149684e77ae88726" -} diff --git a/.sqlx/query-e358d2799f31f9ed8374fa480bf5d431370a39d38eda251ab9462e6bf7eba642.json b/.sqlx/query-e358d2799f31f9ed8374fa480bf5d431370a39d38eda251ab9462e6bf7eba642.json new file mode 100644 index 0000000000..820c4c4a5e --- /dev/null +++ b/.sqlx/query-e358d2799f31f9ed8374fa480bf5d431370a39d38eda251ab9462e6bf7eba642.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT g.name name, COALESCE(ARRAY_AGG(DISTINCT u.username) FILTER (WHERE u.username IS NOT NULL), '{}') \"members!\", COALESCE(ARRAY_AGG(DISTINCT wn.name) FILTER (WHERE wn.name IS NOT NULL), '{}') \"vpn_locations!\" FROM \"group\" g LEFT JOIN \"group_user\" gu ON gu.group_id = g.id LEFT JOIN \"user\" u ON u.id = gu.user_id LEFT JOIN \"wireguard_network_allowed_group\" wnag ON wnag.group_id = g.id LEFT JOIN \"wireguard_network\" wn ON wn.id = wnag.network_id GROUP BY g.name", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "members!", + "type_info": "TextArray" + }, + { + "ordinal": 2, + "name": "vpn_locations!", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + null, + null + ] + }, + "hash": "e358d2799f31f9ed8374fa480bf5d431370a39d38eda251ab9462e6bf7eba642" +} diff --git a/.sqlx/query-9f7a75ac4a3c5b767746a836e409e3e3f02a029cf9456bed8c5291f92f1d5f8a.json b/.sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json similarity index 80% rename from .sqlx/query-9f7a75ac4a3c5b767746a836e409e3e3f02a029cf9456bed8c5291f92f1d5f8a.json rename to .sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json index 2c51558733..d72c99d113 100644 --- a/.sqlx/query-9f7a75ac4a3c5b767746a836e409e3e3f02a029cf9456bed8c5291f92f1d5f8a.json +++ b/.sqlx/query-e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, address, name, chain_id, challenge_message, challenge_signature, creation_timestamp, validation_timestamp, use_for_mfa FROM wallet WHERE user_id = $1 AND address = $2", + "query": "SELECT id, user_id, address, name, chain_id, challenge_message, challenge_signature, creation_timestamp, validation_timestamp, use_for_mfa FROM wallet WHERE user_id = $1 AND address = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -73,5 +73,5 @@ false ] }, - "hash": "9f7a75ac4a3c5b767746a836e409e3e3f02a029cf9456bed8c5291f92f1d5f8a" + "hash": "e48af3ee182c0f3829a2248faf705626b1ad29755fef9758f88353e786a1b2ed" } diff --git a/.sqlx/query-7fa302170061ee58a46152f581f13dbae26b7dd6ba59169cb5a18d443d039c14.json b/.sqlx/query-e52ba8f1cdce5ef217602017fe59ee0038f6b5239ff6cb7e9d3acccd6697de00.json similarity index 79% rename from .sqlx/query-7fa302170061ee58a46152f581f13dbae26b7dd6ba59169cb5a18d443d039c14.json rename to .sqlx/query-e52ba8f1cdce5ef217602017fe59ee0038f6b5239ff6cb7e9d3acccd6697de00.json index e11dbc2056..16a9a4abe5 100644 --- a/.sqlx/query-7fa302170061ee58a46152f581f13dbae26b7dd6ba59169cb5a18d443d039c14.json +++ b/.sqlx/query-e52ba8f1cdce5ef217602017fe59ee0038f6b5239ff6cb7e9d3acccd6697de00.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"url\",\"description\",\"token\",\"enabled\",\"on_user_created\",\"on_user_deleted\",\"on_user_modified\",\"on_hwkey_provision\" FROM \"webhook\" WHERE id = $1", + "query": "SELECT id, \"url\",\"description\",\"token\",\"enabled\",\"on_user_created\",\"on_user_deleted\",\"on_user_modified\",\"on_hwkey_provision\" FROM \"webhook\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ false ] }, - "hash": "7fa302170061ee58a46152f581f13dbae26b7dd6ba59169cb5a18d443d039c14" + "hash": "e52ba8f1cdce5ef217602017fe59ee0038f6b5239ff6cb7e9d3acccd6697de00" } diff --git a/.sqlx/query-06f847d99d452dafd10f4a6bec309c330d76eb5d0a34012bd75395b13e3ff659.json b/.sqlx/query-e6537b70e7f27eb6103a7715d520ec1bb8491908a059422e751592b7da96028f.json similarity index 76% rename from .sqlx/query-06f847d99d452dafd10f4a6bec309c330d76eb5d0a34012bd75395b13e3ff659.json rename to .sqlx/query-e6537b70e7f27eb6103a7715d520ec1bb8491908a059422e751592b7da96028f.json index c291b5a4e6..c531cd830d 100644 --- a/.sqlx/query-06f847d99d452dafd10f4a6bec309c330d76eb5d0a34012bd75395b13e3ff659.json +++ b/.sqlx/query-e6537b70e7f27eb6103a7715d520ec1bb8491908a059422e751592b7da96028f.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"wireguard_pubkey\",\"user_id\",\"created\" FROM \"device\"", + "query": "SELECT id, \"name\",\"wireguard_pubkey\",\"user_id\",\"created\" FROM \"device\"", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -40,5 +40,5 @@ false ] }, - "hash": "06f847d99d452dafd10f4a6bec309c330d76eb5d0a34012bd75395b13e3ff659" + "hash": "e6537b70e7f27eb6103a7715d520ec1bb8491908a059422e751592b7da96028f" } diff --git a/.sqlx/query-3d5d8b6f640435a1986561c73cd809038cdddfb44133b97b5ba7392573239539.json b/.sqlx/query-e699a4be7c892b6c3fa44c41970381f11072cba02dd59f7e5fb7f4925e90692b.json similarity index 52% rename from .sqlx/query-3d5d8b6f640435a1986561c73cd809038cdddfb44133b97b5ba7392573239539.json rename to .sqlx/query-e699a4be7c892b6c3fa44c41970381f11072cba02dd59f7e5fb7f4925e90692b.json index 3dd50e700e..f94df73709 100644 --- a/.sqlx/query-3d5d8b6f640435a1986561c73cd809038cdddfb44133b97b5ba7392573239539.json +++ b/.sqlx/query-e699a4be7c892b6c3fa44c41970381f11072cba02dd59f7e5fb7f4925e90692b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT COALESCE(COUNT(DISTINCT(u.id)), 0) as \"active_users!\", COALESCE(COUNT(DISTINCT(s.device_id)), 0) as \"active_devices!\" FROM \"user\" u JOIN device d ON d.user_id = u.id JOIN wireguard_peer_stats s ON s.device_id = d.id WHERE latest_handshake >= $1 AND s.network = $2", + "query": "SELECT COALESCE(COUNT(DISTINCT(u.id)), 0) \"active_users!\", COALESCE(COUNT(DISTINCT(s.device_id)), 0) \"active_devices!\" FROM \"user\" u JOIN device d ON d.user_id = u.id JOIN wireguard_peer_stats s ON s.device_id = d.id WHERE latest_handshake >= $1 AND s.network = $2", "describe": { "columns": [ { @@ -25,5 +25,5 @@ null ] }, - "hash": "3d5d8b6f640435a1986561c73cd809038cdddfb44133b97b5ba7392573239539" + "hash": "e699a4be7c892b6c3fa44c41970381f11072cba02dd59f7e5fb7f4925e90692b" } diff --git a/.sqlx/query-eb753d506ce15b17b7aca1f5dd3dd03b382a667f0f0bc506e0cbc45118eea293.json b/.sqlx/query-eb753d506ce15b17b7aca1f5dd3dd03b382a667f0f0bc506e0cbc45118eea293.json deleted file mode 100644 index f23df5d702..0000000000 --- a/.sqlx/query-eb753d506ce15b17b7aca1f5dd3dd03b382a667f0f0bc506e0cbc45118eea293.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT g.name as name, COALESCE(ARRAY_AGG(DISTINCT u.username) FILTER (WHERE u.username IS NOT NULL), '{}') as \"members!\", COALESCE(ARRAY_AGG(DISTINCT wn.name) FILTER (WHERE wn.name IS NOT NULL), '{}') as \"vpn_locations!\" FROM \"group\" g LEFT JOIN \"group_user\" gu ON gu.group_id = g.id LEFT JOIN \"user\" u ON u.id = gu.user_id LEFT JOIN \"wireguard_network_allowed_group\" wnag ON wnag.group_id = g.id LEFT JOIN \"wireguard_network\" wn ON wn.id = wnag.network_id GROUP BY g.name", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "members!", - "type_info": "TextArray" - }, - { - "ordinal": 2, - "name": "vpn_locations!", - "type_info": "TextArray" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - null, - null - ] - }, - "hash": "eb753d506ce15b17b7aca1f5dd3dd03b382a667f0f0bc506e0cbc45118eea293" -} diff --git a/.sqlx/query-dc21ad55a35e5ea826e64030b4d71a0df499cc1b22f1d8fcffa135307b09c187.json b/.sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json similarity index 79% rename from .sqlx/query-dc21ad55a35e5ea826e64030b4d71a0df499cc1b22f1d8fcffa135307b09c187.json rename to .sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json index 9462acb1b7..94f69bdb1d 100644 --- a/.sqlx/query-dc21ad55a35e5ea826e64030b4d71a0df499cc1b22f1d8fcffa135307b09c187.json +++ b/.sqlx/query-edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\" WHERE id = $1", + "query": "SELECT id, \"device_id\",\"collected_at\",\"network\",\"endpoint\",\"upload\",\"download\",\"latest_handshake\",\"allowed_ips\" FROM \"wireguard_peer_stats\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ true ] }, - "hash": "dc21ad55a35e5ea826e64030b4d71a0df499cc1b22f1d8fcffa135307b09c187" + "hash": "edb099ee36c93c25aa9f91afdbf0c673f870e9d8c619826f855c7090a7d3cf30" } diff --git a/.sqlx/query-5f1da7400599669d9591f6dded6c38d6f74286fd5660c1ccae20ce43617bbc8f.json b/.sqlx/query-f323dc6850824f6b51c4b980d330cc93402cd94cdd47d80ada08063d0cb708b7.json similarity index 75% rename from .sqlx/query-5f1da7400599669d9591f6dded6c38d6f74286fd5660c1ccae20ce43617bbc8f.json rename to .sqlx/query-f323dc6850824f6b51c4b980d330cc93402cd94cdd47d80ada08063d0cb708b7.json index fbd0180007..aaddaa63c6 100644 --- a/.sqlx/query-5f1da7400599669d9591f6dded6c38d6f74286fd5660c1ccae20ce43617bbc8f.json +++ b/.sqlx/query-f323dc6850824f6b51c4b980d330cc93402cd94cdd47d80ada08063d0cb708b7.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "WITH s AS ( SELECT DISTINCT ON (device_id) * FROM wireguard_peer_stats ORDER BY device_id, latest_handshake DESC ) SELECT d.id \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN s ON d.id = s.device_id WHERE s.latest_handshake >= $1 AND s.network = $2", + "query": "WITH s AS ( SELECT DISTINCT ON (device_id) * FROM wireguard_peer_stats ORDER BY device_id, latest_handshake DESC ) SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN s ON d.id = s.device_id WHERE s.latest_handshake >= $1 AND s.network = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -43,5 +43,5 @@ false ] }, - "hash": "5f1da7400599669d9591f6dded6c38d6f74286fd5660c1ccae20ce43617bbc8f" + "hash": "f323dc6850824f6b51c4b980d330cc93402cd94cdd47d80ada08063d0cb708b7" } diff --git a/.sqlx/query-eb6dee5462657ac5ce0ecf31d1477ce7cc874d9ad8b3119977168f601b3e8072.json b/.sqlx/query-f5d791e2cff6d6fec9ad6c18b279b2237153a82d6057bbb59dbf4d8b22f18cae.json similarity index 76% rename from .sqlx/query-eb6dee5462657ac5ce0ecf31d1477ce7cc874d9ad8b3119977168f601b3e8072.json rename to .sqlx/query-f5d791e2cff6d6fec9ad6c18b279b2237153a82d6057bbb59dbf4d8b22f18cae.json index b0e4fe52ad..595be54839 100644 --- a/.sqlx/query-eb6dee5462657ac5ce0ecf31d1477ce7cc874d9ad8b3119977168f601b3e8072.json +++ b/.sqlx/query-f5d791e2cff6d6fec9ad6c18b279b2237153a82d6057bbb59dbf4d8b22f18cae.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", name, wireguard_pubkey, user_id, created FROM device WHERE wireguard_pubkey = $1", + "query": "SELECT id, name, wireguard_pubkey, user_id, created FROM device WHERE wireguard_pubkey = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "eb6dee5462657ac5ce0ecf31d1477ce7cc874d9ad8b3119977168f601b3e8072" + "hash": "f5d791e2cff6d6fec9ad6c18b279b2237153a82d6057bbb59dbf4d8b22f18cae" } diff --git a/.sqlx/query-c64f247f81e332689e35c224656847b246deb6f92a5790a4fdd0d5733defbb57.json b/.sqlx/query-f74ae18765935516c2d99efe7a5b1257994186057c0f00bc4e2c2a401e7ddaae.json similarity index 75% rename from .sqlx/query-c64f247f81e332689e35c224656847b246deb6f92a5790a4fdd0d5733defbb57.json rename to .sqlx/query-f74ae18765935516c2d99efe7a5b1257994186057c0f00bc4e2c2a401e7ddaae.json index 4a03705f0c..881bb89e74 100644 --- a/.sqlx/query-c64f247f81e332689e35c224656847b246deb6f92a5790a4fdd0d5733defbb57.json +++ b/.sqlx/query-f74ae18765935516c2d99efe7a5b1257994186057c0f00bc4e2c2a401e7ddaae.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"wireguard_pubkey\",\"user_id\",\"created\" FROM \"device\" WHERE id = $1", + "query": "SELECT device.id, name, wireguard_pubkey, user_id, created FROM device WHERE user_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -42,5 +42,5 @@ false ] }, - "hash": "c64f247f81e332689e35c224656847b246deb6f92a5790a4fdd0d5733defbb57" + "hash": "f74ae18765935516c2d99efe7a5b1257994186057c0f00bc4e2c2a401e7ddaae" } diff --git a/.sqlx/query-0739dda752a2a5b4bb6483d8fdc87b7065d01d6d5990c7d344be2d0068cac835.json b/.sqlx/query-f76434209cbb430ea2c66f7d97bfca592d7fbd12303935281ff30de901a30870.json similarity index 79% rename from .sqlx/query-0739dda752a2a5b4bb6483d8fdc87b7065d01d6d5990c7d344be2d0068cac835.json rename to .sqlx/query-f76434209cbb430ea2c66f7d97bfca592d7fbd12303935281ff30de901a30870.json index e0722c5555..b83ac646f4 100644 --- a/.sqlx/query-0739dda752a2a5b4bb6483d8fdc87b7065d01d6d5990c7d344be2d0068cac835.json +++ b/.sqlx/query-f76434209cbb430ea2c66f7d97bfca592d7fbd12303935281ff30de901a30870.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1", + "query": "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled FROM oauth2client WHERE client_id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -54,5 +54,5 @@ false ] }, - "hash": "0739dda752a2a5b4bb6483d8fdc87b7065d01d6d5990c7d344be2d0068cac835" + "hash": "f76434209cbb430ea2c66f7d97bfca592d7fbd12303935281ff30de901a30870" } diff --git a/.sqlx/query-f93c26d4777db959e48d8e33a08f081dafeefcc853b610ac238134ec78043a68.json b/.sqlx/query-f93c26d4777db959e48d8e33a08f081dafeefcc853b610ac238134ec78043a68.json new file mode 100644 index 0000000000..526554820e --- /dev/null +++ b/.sqlx/query-f93c26d4777db959e48d8e33a08f081dafeefcc853b610ac238134ec78043a68.json @@ -0,0 +1,46 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT DISTINCT ON (d.id) d.id, d.name, d.wireguard_pubkey, d.user_id, d.created FROM device d JOIN \"user\" u ON d.user_id = u.id JOIN group_user gu ON u.id = gu.user_id JOIN \"group\" g ON gu.group_id = g.id WHERE g.\"name\" IN (SELECT * FROM UNNEST($1::text[])) AND u.is_active = true ORDER BY d.id ASC", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "wireguard_pubkey", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "user_id", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "created", + "type_info": "Timestamp" + } + ], + "parameters": { + "Left": [ + "TextArray" + ] + }, + "nullable": [ + false, + false, + false, + false, + false + ] + }, + "hash": "f93c26d4777db959e48d8e33a08f081dafeefcc853b610ac238134ec78043a68" +} diff --git a/.sqlx/query-29809d24769e1c6cb572c666fd0caeeaeb3b13a524f503b21414d1394173eb24.json b/.sqlx/query-f95b0887794cfac8c96bc32a99c3ea4a8e635965c409d72d678045abf28bd16f.json similarity index 80% rename from .sqlx/query-29809d24769e1c6cb572c666fd0caeeaeb3b13a524f503b21414d1394173eb24.json rename to .sqlx/query-f95b0887794cfac8c96bc32a99c3ea4a8e635965c409d72d678045abf28bd16f.json index ffaf6c210d..a31da94fa7 100644 --- a/.sqlx/query-29809d24769e1c6cb572c666fd0caeeaeb3b13a524f503b21414d1394173eb24.json +++ b/.sqlx/query-f95b0887794cfac8c96bc32a99c3ea4a8e635965c409d72d678045abf28bd16f.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"mfa_enabled\",\"keepalive_interval\",\"peer_disconnect_threshold\" FROM \"wireguard_network\" WHERE id = $1", + "query": "SELECT id, \"name\",\"address\" \"address: _\",\"port\",\"pubkey\",\"prvkey\",\"endpoint\",\"dns\",\"allowed_ips\" \"allowed_ips: _\",\"connected_at\",\"mfa_enabled\",\"keepalive_interval\",\"peer_disconnect_threshold\" FROM \"wireguard_network\" WHERE id = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -90,5 +90,5 @@ false ] }, - "hash": "29809d24769e1c6cb572c666fd0caeeaeb3b13a524f503b21414d1394173eb24" + "hash": "f95b0887794cfac8c96bc32a99c3ea4a8e635965c409d72d678045abf28bd16f" } diff --git a/.sqlx/query-33e91546890789d9341b06c541648149baf908b4f2ba35da8adc07feda6299e3.json b/.sqlx/query-f9617d86a16b2a7fddac1f76089a2b85c6a034d77aa2dcd1855665dce3858566.json similarity index 81% rename from .sqlx/query-33e91546890789d9341b06c541648149baf908b4f2ba35da8adc07feda6299e3.json rename to .sqlx/query-f9617d86a16b2a7fddac1f76089a2b85c6a034d77aa2dcd1855665dce3858566.json index 694ea07505..a7f156e9b8 100644 --- a/.sqlx/query-33e91546890789d9341b06c541648149baf908b4f2ba35da8adc07feda6299e3.json +++ b/.sqlx/query-f9617d86a16b2a7fddac1f76089a2b85c6a034d77aa2dcd1855665dce3858566.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", url, description, token, enabled, on_user_created, on_user_deleted, on_user_modified, on_hwkey_provision FROM webhook WHERE url = $1", + "query": "SELECT id, url, description, token, enabled, on_user_created, on_user_deleted, on_user_modified, on_hwkey_provision FROM webhook WHERE url = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -66,5 +66,5 @@ false ] }, - "hash": "33e91546890789d9341b06c541648149baf908b4f2ba35da8adc07feda6299e3" + "hash": "f9617d86a16b2a7fddac1f76089a2b85c6a034d77aa2dcd1855665dce3858566" } diff --git a/.sqlx/query-2163bfa0df95cc31d0325f2a2a6521ef7e3b701ba5a97a74d5e6b3637c626dda.json b/.sqlx/query-fac46fc03161bac460c30d9b61af52e6a872f5e974f586ed4d6a8c1ceaed8223.json similarity index 53% rename from .sqlx/query-2163bfa0df95cc31d0325f2a2a6521ef7e3b701ba5a97a74d5e6b3637c626dda.json rename to .sqlx/query-fac46fc03161bac460c30d9b61af52e6a872f5e974f586ed4d6a8c1ceaed8223.json index f83e899585..7af831b096 100644 --- a/.sqlx/query-2163bfa0df95cc31d0325f2a2a6521ef7e3b701ba5a97a74d5e6b3637c626dda.json +++ b/.sqlx/query-fac46fc03161bac460c30d9b61af52e6a872f5e974f586ed4d6a8c1ceaed8223.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "DELETE FROM \"settings\" WHERE id = $1", + "query": "DELETE FROM \"pollingtoken\" WHERE id = $1", "describe": { "columns": [], "parameters": { @@ -10,5 +10,5 @@ }, "nullable": [] }, - "hash": "2163bfa0df95cc31d0325f2a2a6521ef7e3b701ba5a97a74d5e6b3637c626dda" + "hash": "fac46fc03161bac460c30d9b61af52e6a872f5e974f586ed4d6a8c1ceaed8223" } diff --git a/.sqlx/query-06e4ad525d72c83281944a311063f8717344fcb0c8ead0e98981439887e8e366.json b/.sqlx/query-fad6990b8d347099568fa0e867a30923a54812064de0b9331b2c71134e6ce29e.json similarity index 81% rename from .sqlx/query-06e4ad525d72c83281944a311063f8717344fcb0c8ead0e98981439887e8e366.json rename to .sqlx/query-fad6990b8d347099568fa0e867a30923a54812064de0b9331b2c71134e6ce29e.json index 8b708fd22d..0852c267c0 100644 --- a/.sqlx/query-06e4ad525d72c83281944a311063f8717344fcb0c8ead0e98981439887e8e366.json +++ b/.sqlx/query-fad6990b8d347099568fa0e867a30923a54812064de0b9331b2c71134e6ce29e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE device_id = $1", + "query": "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at FROM wireguard_network_device WHERE device_id = $1", "describe": { "columns": [ { @@ -48,5 +48,5 @@ true ] }, - "hash": "06e4ad525d72c83281944a311063f8717344fcb0c8ead0e98981439887e8e366" + "hash": "fad6990b8d347099568fa0e867a30923a54812064de0b9331b2c71134e6ce29e" } diff --git a/.sqlx/query-6fc45edc38cf3f590daec8f930a4117ae77dd226c42ee175544f0ba5eccb315d.json b/.sqlx/query-fc807754b75355939c8b77602eb9a691cc5d4f228326a0ffdb0cfcdedee438a5.json similarity index 81% rename from .sqlx/query-6fc45edc38cf3f590daec8f930a4117ae77dd226c42ee175544f0ba5eccb315d.json rename to .sqlx/query-fc807754b75355939c8b77602eb9a691cc5d4f228326a0ffdb0cfcdedee438a5.json index 256dd6ac1c..36de06fc58 100644 --- a/.sqlx/query-6fc45edc38cf3f590daec8f930a4117ae77dd226c42ee175544f0ba5eccb315d.json +++ b/.sqlx/query-fc807754b75355939c8b77602eb9a691cc5d4f228326a0ffdb0cfcdedee438a5.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active FROM \"user\" WHERE email = $1", + "query": "SELECT id, username, password_hash, last_name, first_name, email, phone, mfa_enabled, totp_enabled, email_mfa_enabled, totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub FROM \"user\" WHERE username = $1", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -90,6 +90,11 @@ "ordinal": 14, "name": "is_active", "type_info": "Bool" + }, + { + "ordinal": 15, + "name": "openid_sub", + "type_info": "Text" } ], "parameters": { @@ -112,8 +117,9 @@ true, false, false, - false + false, + true ] }, - "hash": "6fc45edc38cf3f590daec8f930a4117ae77dd226c42ee175544f0ba5eccb315d" + "hash": "fc807754b75355939c8b77602eb9a691cc5d4f228326a0ffdb0cfcdedee438a5" } diff --git a/.sqlx/query-8e0dd4e8088a1d51fc94e9e013d6220677464886b055722182a89a2a60605db6.json b/.sqlx/query-fcd1e222d97e81825c3f6f43a731b6cf8647bc5ebb766f1aaa5b69a62cb92759.json similarity index 81% rename from .sqlx/query-8e0dd4e8088a1d51fc94e9e013d6220677464886b055722182a89a2a60605db6.json rename to .sqlx/query-fcd1e222d97e81825c3f6f43a731b6cf8647bc5ebb766f1aaa5b69a62cb92759.json index 35cc853b1d..109ad26022 100644 --- a/.sqlx/query-8e0dd4e8088a1d51fc94e9e013d6220677464886b055722182a89a2a60605db6.json +++ b/.sqlx/query-fcd1e222d97e81825c3f6f43a731b6cf8647bc5ebb766f1aaa5b69a62cb92759.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT id \"id?\", user_id, yubikey_id \"yubikey_id?\", key, name, key_type \"key_type: AuthenticationKeyType\" FROM authentication_key WHERE user_id = $1 AND key_type = $2", + "query": "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, name, key_type \"key_type: AuthenticationKeyType\" FROM authentication_key WHERE user_id = $1 AND key_type = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "id?", + "name": "id", "type_info": "Int8" }, { @@ -69,5 +69,5 @@ false ] }, - "hash": "8e0dd4e8088a1d51fc94e9e013d6220677464886b055722182a89a2a60605db6" + "hash": "fcd1e222d97e81825c3f6f43a731b6cf8647bc5ebb766f1aaa5b69a62cb92759" } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 69aad48aa9..dcb63746a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,3 +50,10 @@ Following environment variables can be set to configure orion core service: ### HTTP server * **DEFGUARD_WEB_PORT**: web services bind port, default = `8000` + + +### User agents YAML update + +``` +curl -Lf https://raw.githubusercontent.com/ua-parser/uap-core/master/regexes.yaml | yq -y '.' > user_agent_header_regexes.yaml +``` diff --git a/Cargo.lock b/Cargo.lock index 860fa5b05e..0a8671c166 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" @@ -59,7 +59,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -97,9 +96,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -112,33 +111,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -146,9 +145,18 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "arbitrary" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "argon2" @@ -164,9 +172,9 @@ dependencies = [ [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "asn1-rs" @@ -193,7 +201,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "synstructure", + "synstructure 0.12.6", ] [[package]] @@ -209,9 +217,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -220,24 +228,24 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -257,14 +265,14 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" @@ -279,7 +287,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "itoa", "matchit", "memchr", @@ -289,25 +297,25 @@ dependencies = [ "rustversion", "serde", "sync_wrapper 0.1.2", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", ] [[package]] name = "axum" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", - "axum-core 0.4.3", + "axum-core 0.4.5", "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", - "hyper 1.4.0", + "hyper 1.4.1", "hyper-util", "itoa", "matchit", @@ -322,7 +330,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.1", "tokio", - "tower", + "tower 0.5.1", "tower-layer", "tower-service", "tracing", @@ -334,7 +342,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e7c467bdcd2bd982ce5c8742a1a178aba7b03db399fd18f5d5d438f5aa91cb4" dependencies = [ - "axum 0.7.5", + "axum 0.7.7", "forwarded-header-value", "serde", ] @@ -358,20 +366,20 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.1", "tower-layer", "tower-service", "tracing", @@ -379,23 +387,23 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" +checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9" dependencies = [ - "axum 0.7.5", - "axum-core 0.4.3", + "axum 0.7.7", + "axum-core 0.4.5", "bytes", "cookie 0.18.1", "futures-util", "headers", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "serde", - "tower", + "tower 0.5.1", "tower-layer", "tower-service", "tracing", @@ -403,17 +411,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -424,9 +432,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base32" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" +checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" [[package]] name = "base64" @@ -474,6 +482,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "1.3.2" @@ -519,16 +533,44 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "bstr" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "serde", ] +[[package]] +name = "buffer-redux" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8acf87c5b9f5897cd3ebb9a327f420e0cae9dd4e5c1d2e36f2c84c571a58f1" +dependencies = [ + "memchr", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -549,18 +591,51 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" dependencies = [ "serde", ] +[[package]] +name = "camellia" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "cast5" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" +dependencies = [ + "cipher", +] + [[package]] name = "cc" -version = "1.0.104" +version = "1.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfb-mode" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" +checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" +dependencies = [ + "cipher", +] [[package]] name = "cfg-if" @@ -636,9 +711,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.8" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -646,9 +721,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -658,27 +733,38 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "cmac" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" +dependencies = [ + "cipher", + "dbl", + "digest", +] [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "compact_jwt" @@ -697,11 +783,20 @@ dependencies = [ "uuid", ] +[[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-hex" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" dependencies = [ "cfg-if", "cpufeatures", @@ -797,15 +892,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -825,6 +920,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc24" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" + [[package]] name = "crc32fast" version = "1.4.2" @@ -930,14 +1031,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] name = "darling" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -945,27 +1046,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] name = "darling_macro" -version = "0.20.9" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -974,15 +1075,25 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +dependencies = [ + "generic-array", +] + [[package]] name = "defguard" -version = "0.11.0" +version = "1.0.0" dependencies = [ "anyhow", "argon2", - "axum 0.7.5", + "axum 0.7.7", "axum-client-ip", "axum-extra", + "base32", "base64 0.22.1", "bytes", "chrono", @@ -1000,14 +1111,14 @@ dependencies = [ "mime_guess", "model_derive", "openidconnect", - "otpauth", + "pgp", "prost", "prost-build", "pulldown-cmark", "rand", "rand_core", "regex", - "reqwest", + "reqwest 0.11.27", "rsa", "rust-embed", "rust-ini", @@ -1030,11 +1141,16 @@ dependencies = [ "tokio-stream", "tonic", "tonic-build", + "tonic-health", + "totp-lite", "tower-http", "tracing", "tracing-subscriber", "uaparser", + "utoipa", + "utoipa-swagger-ui", "uuid", + "vergen-git2", "webauthn-authenticator-rs", "webauthn-rs", "webauthn-rs-proto", @@ -1076,6 +1192,48 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "derive_builder" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" +dependencies = [ + "derive_builder_core", + "syn 2.0.79", +] + [[package]] name = "derive_more" version = "0.99.18" @@ -1086,7 +1244,16 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.68", + "syn 2.0.79", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", ] [[package]] @@ -1115,7 +1282,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -1133,12 +1300,41 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dsa" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" +dependencies = [ + "digest", + "num-bigint-dig", + "num-traits", + "pkcs8", + "rfc6979", + "sha2", + "signature", + "zeroize", +] + [[package]] name = "dyn-clone" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "eax" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" +dependencies = [ + "aead", + "cipher", + "cmac", + "ctr", + "subtle", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -1219,9 +1415,9 @@ dependencies = [ [[package]] name = "email_address" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1019fa28f600f5b581b7a603d515c3f1635da041ca211b5055804788673abfe" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" [[package]] name = "encoding_rs" @@ -1336,15 +1532,20 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "ff" @@ -1382,9 +1583,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -1398,7 +1599,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "spin 0.9.8", + "spin", ] [[package]] @@ -1514,7 +1715,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -1592,21 +1793,34 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + +[[package]] +name = "git2" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +dependencies = [ + "bitflags 2.6.0", + "libc", + "libgit2-sys", + "log", + "url", +] [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -1643,7 +1857,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.2.6", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -1672,11 +1886,17 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown 0.14.5", ] @@ -1705,15 +1925,6 @@ dependencies = [ "http 1.1.0", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -1805,9 +2016,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http 1.1.0", @@ -1822,7 +2033,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1834,9 +2045,9 @@ checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1861,9 +2072,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.29" +version = "0.14.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" dependencies = [ "bytes", "futures-channel", @@ -1885,21 +2096,22 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] @@ -1910,19 +2122,37 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.29", + "hyper 0.14.30", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.23.13", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots 0.26.6", +] + [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.29", + "hyper 0.14.30", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1935,7 +2165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.29", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", @@ -1943,24 +2173,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", - "http-body 1.0.0", - "hyper 1.4.0", + "http-body 1.0.1", + "hyper 1.4.1", "pin-project-lite", + "socket2", "tokio", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1980,50 +2214,189 @@ dependencies = [ ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "idna" -version = "0.3.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "ignore" -version = "0.4.22" +name = "icu_locid_transform_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata 0.4.7", - "same-file", - "walkdir", - "winapi-util", -] +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] -name = "impl-codec" -version = "0.6.0" +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "idea" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" +dependencies = [ + "cipher", +] + +[[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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[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 = "idna" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd" +dependencies = [ + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata 0.4.8", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ @@ -2072,12 +2445,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -2092,9 +2465,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "ipnetwork" @@ -2107,9 +2480,15 @@ dependencies = [ [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "iter-read" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071ed4cc1afd86650602c7b11aa2e1ce30762a1c27193201cb5cee9c6ebb1294" [[package]] name = "itertools" @@ -2135,11 +2514,20 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -2153,7 +2541,7 @@ dependencies = [ "base64 0.21.7", "js-sys", "pem", - "ring 0.17.8", + "ring", "serde", "serde_json", "simple_asn1", @@ -2161,15 +2549,16 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", "once_cell", "sha2", + "signature", ] [[package]] @@ -2187,7 +2576,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -2226,9 +2615,9 @@ dependencies = [ [[package]] name = "lettre" -version = "0.11.7" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a62049a808f1c4e2356a2a380bd5f2aca3b011b0b482cf3b914ba1731426969" +checksum = "69f204773bab09b150320ea1c83db41dc6ee606a4bc36dc1f43005fe7b58ce06" dependencies = [ "async-trait", "base64 0.22.1", @@ -2240,7 +2629,7 @@ dependencies = [ "futures-util", "hostname", "httpdate", - "idna 0.5.0", + "idna 1.0.2", "mime", "native-tls", "nom", @@ -2254,9 +2643,21 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libgit2-sys" +version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] [[package]] name = "libm" @@ -2266,21 +2667,39 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] +[[package]] +name = "libz-sys" +version = "1.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -2367,30 +2786,31 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "model_derive" -version = "0.1.2" +version = "0.2.0" dependencies = [ "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -2465,6 +2885,7 @@ dependencies = [ "num-iter", "num-traits", "rand", + "serde", "smallvec", "zeroize", ] @@ -2516,35 +2937,34 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", ] [[package]] @@ -2558,6 +2978,7 @@ dependencies = [ "getrandom", "http 0.2.12", "rand", + "reqwest 0.11.27", "serde", "serde_json", "serde_path_to_error", @@ -2568,13 +2989,25 @@ dependencies = [ [[package]] name = "object" -version = "0.36.1" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] +[[package]] +name = "ocb3" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + [[package]] name = "oid-registry" version = "0.4.0" @@ -2586,9 +3019,12 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "opaque-debug" @@ -2655,9 +3091,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -2676,7 +3112,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -2687,9 +3123,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -2716,17 +3152,6 @@ dependencies = [ "hashbrown 0.14.5", ] -[[package]] -name = "otpauth" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca79a3dc8a04388203b57a706221f2d98b5be8ad85db486e6f995777c35ae25" -dependencies = [ - "base32", - "byteorder", - "ring 0.16.20", -] - [[package]] name = "overload" version = "0.1.1" @@ -2797,6 +3222,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -2815,7 +3246,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -2873,9 +3304,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -2884,9 +3315,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -2894,22 +3325,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -2923,7 +3354,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.6", + "indexmap 2.6.0", +] + +[[package]] +name = "pgp" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6c842436d5fa2b59eac1e9b3d142b50bfff99c1744c816b1f4c2ac55a20754" +dependencies = [ + "aes", + "aes-gcm", + "argon2", + "base64 0.22.1", + "bitfield", + "block-padding", + "blowfish", + "bstr", + "buffer-redux", + "byteorder", + "camellia", + "cast5", + "cfb-mode", + "chrono", + "cipher", + "const-oid", + "crc24", + "curve25519-dalek", + "derive_builder", + "des", + "digest", + "dsa", + "eax", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "flate2", + "generic-array", + "hex", + "hkdf", + "idea", + "iter-read", + "k256", + "log", + "md-5", + "nom", + "num-bigint-dig", + "num-traits", + "num_enum", + "ocb3", + "p256", + "p384", + "p521", + "rand", + "ripemd", + "rsa", + "sha1", + "sha1-checked", + "sha2", + "sha3", + "signature", + "smallvec", + "thiserror", + "twofish", + "x25519-dalek", + "zeroize", ] [[package]] @@ -2981,7 +3476,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -3019,9 +3514,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polyval" @@ -3035,6 +3530,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3043,18 +3544,21 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" dependencies = [ "proc-macro2", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -3082,13 +3586,37 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -3110,7 +3638,7 @@ dependencies = [ "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "unarray", ] @@ -3131,7 +3659,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.5.0", + "heck", "itertools 0.12.1", "log", "multimap", @@ -3141,7 +3669,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.68", + "syn 2.0.79", "tempfile", ] @@ -3155,7 +3683,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -3175,9 +3703,9 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "psm" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" dependencies = [ "cc", ] @@ -3194,9 +3722,9 @@ dependencies = [ [[package]] name = "pulldown-cmark" -version = "0.11.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8746739f11d39ce5ad5c2520a9b75285310dbfe78c541ccf832d38615765aec0" +checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625" dependencies = [ "bitflags 2.6.0", "getopts", @@ -3211,20 +3739,68 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.13", + "socket2", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.13", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79ec282e887b434b68c18fe5c121d38e72a5cf35119b59e54ec5b992ea9c8eb0" +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "radium" @@ -3273,32 +3849,23 @@ dependencies = [ [[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_syscall" -version = "0.5.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -3312,13 +3879,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -3329,9 +3896,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -3349,8 +3916,8 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", - "hyper-rustls", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "hyper-tls", "ipnet", "js-sys", @@ -3378,33 +3945,61 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg", ] [[package]] -name = "rfc6979" -version = "0.4.0" +name = "reqwest" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ - "hmac", - "subtle", + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls 0.27.3", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.13", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.26.6", + "windows-registry", ] [[package]] -name = "ring" -version = "0.16.20" +name = "rfc6979" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", + "hmac", + "subtle", ] [[package]] @@ -3417,11 +4012,20 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.52.0", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + [[package]] name = "rlp" version = "0.5.2" @@ -3467,9 +4071,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -3478,22 +4082,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.68", + "syn 2.0.79", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.4.0" +version = "8.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" dependencies = [ "globset", "sha2", @@ -3516,6 +4120,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -3524,9 +4134,9 @@ checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -3542,9 +4152,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -3560,7 +4170,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-webpki 0.101.7", "sct", ] @@ -3572,21 +4182,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.2", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -3603,19 +4227,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -3623,19 +4246,19 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.8", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -3685,11 +4308,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3704,8 +4327,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -3724,9 +4347,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "rand", "secp256k1-sys", @@ -3734,9 +4357,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] @@ -3753,9 +4376,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", @@ -3766,9 +4389,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -3782,9 +4405,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -3820,22 +4443,23 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -3884,15 +4508,15 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.8.3" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377" +checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.2.6", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", @@ -3902,14 +4526,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.3" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703" +checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -3918,7 +4542,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -3947,6 +4571,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha1-checked" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] + [[package]] name = "sha2" version = "0.10.8" @@ -3977,6 +4611,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signature" version = "2.2.0" @@ -4016,9 +4656,9 @@ dependencies = [ [[package]] name = "slug" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ "deunicode", "wasm-bindgen", @@ -4029,6 +4669,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -4040,12 +4683,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[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" @@ -4067,9 +4704,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" dependencies = [ "nom", "unicode_categories", @@ -4077,9 +4714,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -4090,11 +4727,10 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ - "ahash", "atoi", "byteorder", "bytes", @@ -4108,9 +4744,10 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", + "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.2.6", + "indexmap 2.6.0", "ipnetwork", "log", "memchr", @@ -4133,26 +4770,26 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.79", ] [[package]] name = "sqlx-macros-core" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", - "heck 0.4.1", + "heck", "hex", "once_cell", "proc-macro2", @@ -4164,7 +4801,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.79", "tempfile", "tokio", "url", @@ -4172,12 +4809,12 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "byteorder", "bytes", @@ -4216,12 +4853,12 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "byteorder", "chrono", @@ -4257,9 +4894,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "chrono", @@ -4273,10 +4910,10 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", "tracing", "url", - "urlencoding", "uuid", ] @@ -4321,17 +4958,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stacker" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" dependencies = [ "cc", "cfg-if", "libc", "psm", - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -4359,22 +5002,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "struct-patch" -version = "0.4.1" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c52ef523e89b3172242bbabefd8a92493ae5571224c29ed2f00185c39b395c2" +checksum = "82dd71e677fa313d07db38f4c7f9a38f89dfb90be8f35914956919f6ca7b9174" dependencies = [ "struct-patch-derive", ] [[package]] name = "struct-patch-derive" -version = "0.4.1" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f14a349c27ebe59faba22f933c9c734d428da7231e88a247e9d8c61eea964ddb" +checksum = "4596646090f0d724e6c7f3b65d694f99a0daa1a5893a78ef83887025e041405c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -4392,11 +5035,11 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -4418,9 +5061,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -4438,6 +5081,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -4451,6 +5097,17 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -4480,14 +5137,15 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4514,22 +5172,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -4550,7 +5208,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -4582,11 +5242,21 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -4599,20 +5269,19 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4627,13 +5296,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -4667,11 +5336,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.13", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -4681,9 +5361,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -4694,17 +5374,17 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.1" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.2.6", + "indexmap 2.6.0", "toml_datetime", "winnow", ] @@ -4724,18 +5404,18 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.29", + "hyper 0.14.30", "hyper-timeout", "percent-encoding", "pin-project", "prost", "rustls-native-certs", - "rustls-pemfile 2.1.2", + "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -4751,7 +5431,32 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.68", + "syn 2.0.79", +] + +[[package]] +name = "tonic-health" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cef6e24bc96871001a7e48e820ab240b3de2201e59b517cf52835df2f1d2350" +dependencies = [ + "async-stream", + "prost", + "tokio", + "tokio-stream", + "tonic", +] + +[[package]] +name = "totp-lite" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e43134db17199f7f721803383ac5854edd0d3d523cc34dba321d6acfbe76c3" +dependencies = [ + "digest", + "hmac", + "sha1", + "sha2", ] [[package]] @@ -4774,6 +5479,22 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 0.1.2", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-http" version = "0.5.2" @@ -4784,7 +5505,7 @@ dependencies = [ "bytes", "futures-util", "http 1.1.0", - "http-body 1.0.0", + "http-body 1.0.1", "http-body-util", "http-range-header", "httpdate", @@ -4801,15 +5522,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -4831,7 +5552,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", ] [[package]] @@ -4879,6 +5600,15 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "twofish" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" +dependencies = [ + "cipher", +] + [[package]] name = "typenum" version = "1.17.0" @@ -4901,9 +5631,9 @@ dependencies = [ [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -4984,48 +5714,42 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" - -[[package]] -name = "unicode-segmentation" -version = "1.11.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unicode_categories" @@ -5049,12 +5773,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -5074,10 +5792,16 @@ dependencies = [ ] [[package]] -name = "urlencoding" -version = "2.1.3" +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" @@ -5085,11 +5809,54 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf0e16c02bc4bf5322ab65f10ab1149bdbcaa782cba66dc7057370a3f8190be" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.79", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943e0ff606c6d57d410fd5663a4d7c074ab2c5f14ab903b9514565e59fa1189e" +dependencies = [ + "axum 0.7.7", + "mime_guess", + "regex", + "reqwest 0.12.8", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + [[package]] name = "uuid" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", "serde", @@ -5107,11 +5874,50 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vergen" +version = "9.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349ed9e45296a581f455bc18039878f409992999bc1d5da12a6800eb18c8752f" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", + "time", + "vergen-lib", +] + +[[package]] +name = "vergen-git2" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e771aff771c0d7c2f42e434e2766d304d917e29b40f0424e8faaaa936bbc3f29" +dependencies = [ + "anyhow", + "derive_builder", + "git2", + "rustversion", + "time", + "vergen", + "vergen-lib", +] + +[[package]] +name = "vergen-lib" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229eaddb0050920816cf051e619affaf18caa3dd512de8de5839ccbc8e53abb0" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", +] + [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -5146,34 +5952,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -5183,9 +5990,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5193,28 +6000,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -5225,9 +6032,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -5337,13 +6144,22 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall", "wasite", ] @@ -5365,11 +6181,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5397,6 +6213,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[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.48.0" @@ -5415,6 +6261,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -5538,9 +6393,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.40" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -5555,6 +6410,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "wyz" version = "0.5.1" @@ -5594,12 +6461,37 @@ dependencies = [ "time", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -5611,7 +6503,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", + "synstructure 0.13.1", ] [[package]] @@ -5631,5 +6544,43 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.79", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "zip" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.6.0", + "num_enum", + "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index a75dc6a769..7e53e12ec2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "defguard" -version = "0.11.0" +version = "1.0.0" edition = "2021" license = "Apache-2.0" homepage = "https://defguard.net/" @@ -19,6 +19,7 @@ axum-extra = { version = "0.9", features = [ "cookie-private", "typed-header", ] } +base32 = "0.5" base64 = "0.22" chrono = { version = "0.4", default-features = false, features = [ "clock", @@ -37,8 +38,10 @@ lettre = { version = "0.11", features = ["tokio1", "tokio1-native-tls"] } md4 = "0.10" mime_guess = "2.0" model_derive = { path = "model-derive" } -openidconnect = { version = "3.5", default-features = false, optional = true } -otpauth = "0.4" +openidconnect = { version = "3.5", default-features = false, optional = true, features = [ + "reqwest", +] } +pgp = "0.13" prost = "0.12" pulldown-cmark = "0.11" rand = "0.8" @@ -48,7 +51,7 @@ rand_core = { version = "0.6", default-features = false, features = [ # TODO: update reqwest when openidconnect also depends on http >= 1.0. reqwest = { version = "0.11", features = ["json"] } rsa = { version = "0.9", features = ["pem"] } -rust-embed = { version = "8.4", features = ["include-exclude"] } +rust-embed = { version = "8.5", features = ["include-exclude"] } rust-ini = "0.20" secp256k1 = { version = "0.29", features = [ "recovery", @@ -62,7 +65,7 @@ serde_cbor = { version = "0.12.0-dev", package = "serde_cbor_2" } serde_json = "1.0" serde_urlencoded = "0.7" sha-1 = "0.10" -sqlx = { version = "0.7", features = [ +sqlx = { version = "0.8", features = [ "chrono", "ipnetwork", "runtime-tokio-native-tls", @@ -70,7 +73,7 @@ sqlx = { version = "0.7", features = [ "uuid", ] } ssh-key = "0.6" -struct-patch = "0.4" +struct-patch = "0.8" tera = "1.20" thiserror = "1.0" # match axum-extra -> cookies @@ -86,10 +89,15 @@ tokio = { version = "1", features = [ ] } tokio-stream = "0.1" tonic = { version = "0.11", features = ["gzip", "tls", "tls-roots"] } +tonic-health = "0.11" +totp-lite = { version = "2.0" } tower-http = { version = "0.5", features = ["fs", "trace"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } uaparser = "0.6" +# openapi +utoipa = { version = "4", features = ["axum_extras"] } +utoipa-swagger-ui = { version = "7", features = ["axum"] } uuid = { version = "1.9", features = ["v4"] } webauthn-authenticator-rs = { version = "0.5" } webauthn-rs = { version = "0.5", features = [ @@ -116,6 +124,7 @@ webauthn-authenticator-rs = { version = "0.5", features = ["softpasskey"] } [build-dependencies] prost-build = "0.12" tonic-build = "0.11" +vergen-git2 = { version = "1.0", features = ["build"] } [features] default = ["openid", "wireguard", "worker"] @@ -123,9 +132,6 @@ openid = ["dep:openidconnect"] worker = [] wireguard = [] -[profile.dev] -strip = "debuginfo" - [profile.release] lto = "thin" strip = "symbols" diff --git a/Dockerfile b/Dockerfile index 8762ea0045..f56c358e3d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ COPY web/ . RUN pnpm run generate-translation-types RUN pnpm build -FROM rust:1.77 as chef +FROM rust:1.80 as chef WORKDIR /build @@ -35,6 +35,8 @@ COPY web/src/shared/images/svg ./web/src/shared/images/svg COPY user_agent_header_regexes.yaml /build/user_agent_header_regexes.yaml RUN apt-get update && apt-get -y install protobuf-compiler libprotobuf-dev COPY Cargo.toml Cargo.lock build.rs ./ +# for vergen +COPY .git .git COPY .sqlx .sqlx COPY src src COPY templates templates diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8ddd140946..0000000000 --- a/LICENSE +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2023 teonite ventures sp. z o.o. (teonite) - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..65be775019 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,666 @@ +# Dual license info +The code in this repository is available under a dual licensing model: + +1. Open Source License: The code, except for the contents of the "src/enterprise" directory, is licensed under the AGPL license (this license). This applies to the open core components of the software. +2. Enterprise License: All code in this repository (including within the "src/enterprise" directory) is licensed under a separate Enterprise License (see file src/enterprise/LICENSE.md). + +# GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. + + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +## Preamble + +The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains +free software for all its users. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. + +The precise terms and conditions for copying, distribution and +modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions. + +"This License" refers to version 3 of the GNU Affero General Public +License. + +"Copyright" also means copyright-like laws that apply to other kinds +of works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of +an exact copy. The resulting work is called a "modified version" of +the earlier work or a work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based +on the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user +through a computer network, with no transfer of a copy, is not +conveying. + +An interactive user interface displays "Appropriate Legal Notices" to +the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code. + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of +a work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + +The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can +regenerate automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same +work. + +### 2. Basic Permissions. + +All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, +without conditions so long as your license otherwise remains in force. +You may convey covered works to others for the sole purpose of having +them make modifications exclusively for you, or provide you with +facilities for running those works, provided that you comply with the +terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for +you must do so exclusively on your behalf, under your direction and +control, on terms that prohibit them from making any copies of your +copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes +it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such +circumvention is effected by exercising rights under this License with +respect to the covered work, and you disclaim any intention to limit +operation or modification of the work as a means of enforcing, against +the work's users, your or third parties' legal rights to forbid +circumvention of technological measures. + +### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these +conditions: + +- a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. +- b) The work must carry prominent notices stating that it is + released under this License and any conditions added under + section 7. This requirement modifies the requirement in section 4 + to "keep intact all notices". +- c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. +- d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + +A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of +sections 4 and 5, provided that you also convey the machine-readable +Corresponding Source under the terms of this License, in one of these +ways: + +- a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. +- b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the Corresponding + Source from a network server at no charge. +- c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. +- d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. +- e) Convey the object code using peer-to-peer transmission, + provided you inform other peers where the object code and + Corresponding Source of the work are being offered to the general + public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + +A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, +family, or household purposes, or (2) anything designed or sold for +incorporation into a dwelling. In determining whether a product is a +consumer product, doubtful cases shall be resolved in favor of +coverage. For a particular product received by a particular user, +"normally used" refers to a typical or common use of that class of +product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected +to use, the product. A product is a consumer product regardless of +whether the product has substantial commercial, industrial or +non-consumer uses, unless such uses represent the only significant +mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to +install and execute modified versions of a covered work in that User +Product from a modified version of its Corresponding Source. The +information must suffice to ensure that the continued functioning of +the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or +updates for a work that has been modified or installed by the +recipient, or for the User Product in which it has been modified or +installed. Access to a network may be denied when the modification +itself materially and adversely affects the operation of the network +or violates the rules and protocols for communication across the +network. + +Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + +### 7. Additional Terms. + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders +of that material) supplement the terms of this License with terms: + +- a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or +- b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or +- c) Prohibiting misrepresentation of the origin of that material, + or requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or +- d) Limiting the use for publicity purposes of names of licensors + or authors of the material; or +- e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or +- f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions + of it) with contractual assumptions of liability to the recipient, + for any liability that these contractual assumptions directly + impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; the +above requirements apply either way. + +### 8. Termination. + +You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + +However, if you cease all violation of this License, then your license +from a particular copyright holder is reinstated (a) provisionally, +unless and until the copyright holder explicitly and finally +terminates your license, and (b) permanently, if the copyright holder +fails to notify you of the violation by some reasonable means prior to +60 days after the cessation. + +Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + +### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run +a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + +### 11. Patents. + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned +or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + +If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on +the non-exercise of one or more of the rights that are specifically +granted under this License. You may not convey a covered work if you +are a party to an arrangement with a third party that is in the +business of distributing software, under which you make payment to the +third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties +who would receive the covered work from you, a discriminatory patent +license (a) in connection with copies of the covered work conveyed by +you (or copies made from those copies), or (b) primarily for and in +connection with specific products or compilations that contain the +covered work, unless you entered into that arrangement, or that patent +license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + +### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under +this License and any other pertinent obligations, then as a +consequence you may not convey it at all. For example, if you agree to +terms that obligate you to collect a royalty for further conveying +from those to whom you convey the Program, the only way you could +satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +### 13. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + +### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever +published by the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions +of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + +Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + +### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR +CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR +LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM +TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER +PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively state +the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for +the specific requirements. + +You should also get your employer (if you work as a programmer) or +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. For more information on this, and how to apply and follow +the GNU AGPL, see . diff --git a/README.md b/README.md index 049a45c3e8..4d3bb02209 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ Just follow [this tutorial](http://bit.ly/defguard-setup) [A detailed product roadmap and development status can be found here](https://github.com/orgs/DefGuard/projects/5/views/1) -### ⛑️ Want to help? ⛑️ +### ⛑️ Want to help? ⛑️ Here is a [dedicated view for **good first bugs**](https://github.com/orgs/DefGuard/projects/5/views/5) @@ -149,6 +149,13 @@ See the [documentation](https://defguard.gitbook.io) for more information. Find us on Matrix: [#defguard:teonite.com](https://matrix.to/#/#defguard:teonite.com) +## License + +The code in this repository is available under a dual licensing model: + +1. Open Source License: The code, except for the contents of the "src/enterprise" directory, is licensed under the AGPL license (see file LICENSE.md in this repository). This applies to the open core components of the software. +2. Enterprise License: All code in this repository (including within the "src/enterprise" directory) is licensed under a separate Enterprise License (see file src/enterprise/LICENSE.md). + ## Contribution Please review the [Contributing guide](https://defguard.gitbook.io/defguard/for-developers/contributing) for information on how to get started contributing to the project. You might also find our [environment setup guide](https://defguard.gitbook.io/defguard/for-developers/dev-env-setup) handy. diff --git a/build.rs b/build.rs index 1fda0abc29..43c2aa4dd1 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,10 @@ +use vergen_git2::{Emitter, Git2Builder}; + fn main() -> Result<(), Box> { + // set VERGEN_GIT_SHA env variable based on git commit hash + let git2 = Git2Builder::default().branch(true).sha(true).build()?; + Emitter::default().add_instructions(&git2)?.emit()?; + let mut config = prost_build::Config::new(); config.protoc_arg("--experimental_allow_proto3_optional"); tonic_build::configure().compile_with_config( @@ -7,12 +13,19 @@ fn main() -> Result<(), Box> { "proto/core/auth.proto", "proto/core/proxy.proto", "proto/core/vpn.proto", + "src/enterprise/proto/license.proto", "proto/worker/worker.proto", "proto/wireguard/gateway.proto", ], - &["proto/core", "proto/worker", "proto/wireguard"], + &[ + "proto/core", + "proto/worker", + "proto/wireguard", + "src/enterprise/proto", + ], )?; - println!("cargo:rerun-if-changed=proto"); println!("cargo:rerun-if-changed=migrations"); + println!("cargo:rerun-if-changed=proto"); + println!("cargo:rerun-if-changed=web/dist"); Ok(()) } diff --git a/defguard.service.freebsd b/defguard.service.freebsd new file mode 100644 index 0000000000..d9ae3b89a1 --- /dev/null +++ b/defguard.service.freebsd @@ -0,0 +1,20 @@ +#!/bin/sh + +# PROVIDE: defguard +# REQUIRE: NETWORKING wireguard +# KEYWORD: shutdown + +. /etc/rc.subr + +name="defguard" +rcvar=defguard_enable +command="/usr/local/bin/defguard" +start_cmd="${name}_start" + +defguard_start() +{ + ${command} --config ${config} & +} + +load_rc_config $name +run_rc_command "$1" diff --git a/docker-compose.e2e.yaml b/docker-compose.e2e.yaml index 7b126e6dfa..4d639dbf05 100644 --- a/docker-compose.e2e.yaml +++ b/docker-compose.e2e.yaml @@ -1,4 +1,3 @@ -version: "3.9" services: core: image: ghcr.io/defguard/defguard:current diff --git a/docker-compose.yaml b/docker-compose.yaml index 80f18a65aa..d65f90e405 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,5 +1,3 @@ -version: "3" - services: core: image: ghcr.io/defguard/defguard:latest @@ -7,7 +5,7 @@ services: context: . dockerfile: Dockerfile environment: - DEFGUARD_COOKIE_INSECURE: 'true' + DEFGUARD_COOKIE_INSECURE: "true" DEFGUARD_SECRET_KEY: aa5a506b11d719dd7170f57f5d9947faf8eb0bc2be1325e42aa0237c3dcfd26456e73dff9eef3b12c7bcf8711b45e3e703d8e21ee1c08520f5e12e3f5772da94 DEFGUARD_AUTH_SECRET: defguard-auth-secret DEFGUARD_GATEWAY_SECRET: defguard-gateway-secret diff --git a/drop_test_dbs.sh b/drop_test_dbs.sh index f991182145..f4fa228fe0 100755 --- a/drop_test_dbs.sh +++ b/drop_test_dbs.sh @@ -4,14 +4,13 @@ set -eo pipefail -if [ -f .env ] -then - export $(cat .env | sed 's/#.*//g'| xargs) +if [ -f .env ]; then + export $(sed -e 's/#.*//g' .env | xargs) fi if ! [ -x "$(command -v psql)" ]; then - echo >&2 "Error: psql is not installed." - exit 1 + echo >&2 "Error: psql is not installed." + exit 1 fi if [ -z "${DATABASE_URL}" ]; then @@ -19,7 +18,7 @@ if [ -z "${DATABASE_URL}" ]; then exit 1 fi -PATTERN="[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" +PATTERN='[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}' echo "Dropping test databases" diff --git a/e2e/tests/enrollment.spec.ts b/e2e/tests/enrollment.spec.ts index 265ccf53f4..971016c052 100644 --- a/e2e/tests/enrollment.spec.ts +++ b/e2e/tests/enrollment.spec.ts @@ -74,11 +74,6 @@ test.describe('Create user with enrollment enabled', () => { const deviceResponse = page.waitForResponse('**/create_device'); await createDevice(page); expect((await deviceResponse).status()).toBe(400); - - // Activating the user should fail with a 400 error - const userResponse = page.waitForResponse('**/activate_user'); - await page.getByTestId('enrollment-next').click({ timeout: 2000 }); - expect((await userResponse).status()).toBe(400); }); test('Complete enrollment with created user', async ({ page }) => { diff --git a/e2e/tests/vpn/wizard.spec.ts b/e2e/tests/vpn/wizard.spec.ts index 4e9c597e32..8afb9f29ad 100644 --- a/e2e/tests/vpn/wizard.spec.ts +++ b/e2e/tests/vpn/wizard.spec.ts @@ -37,6 +37,7 @@ test.describe('Setup VPN (wizard) ', () => { ...testUserTemplate, firstName: `test${id}`, username: `test${id}`, + mail: `test${id}@test.com` })); await loginBasic(page, defaultUserAdmin); await apiCreateUsersBulk(page, users); diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..bad9762aba --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1728018373, + "narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "bc947f541ae55e999ffdb4013441347d83b00feb", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..5cc26fee48 --- /dev/null +++ b/flake.nix @@ -0,0 +1,26 @@ +{ + description = "Rust development flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + utils.url = "github:numtide/flake-utils"; + }; + + outputs = { nixpkgs, utils, ... }: + utils.lib.eachDefaultSystem (system: let + pkgs = import nixpkgs {inherit system;}; + in { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + pkg-config + openssl + protobuf + sqlx-cli + cargo + rustc + rust-analyzer + clippy + ]; + }; + }); +} diff --git a/migrations/20240716114732_add_external_openid_login.down.sql b/migrations/20240716114732_add_external_openid_login.down.sql new file mode 100644 index 0000000000..92196a2601 --- /dev/null +++ b/migrations/20240716114732_add_external_openid_login.down.sql @@ -0,0 +1,4 @@ +DROP TABLE openidprovider; +ALTER TABLE "user" DROP CONSTRAINT "user_email_key"; +ALTER TABLE "user" DROP COLUMN "openid_login"; +ALTER TABLE settings DROP COLUMN openid_create_account; diff --git a/migrations/20240716114732_add_external_openid_login.up.sql b/migrations/20240716114732_add_external_openid_login.up.sql new file mode 100644 index 0000000000..169801721f --- /dev/null +++ b/migrations/20240716114732_add_external_openid_login.up.sql @@ -0,0 +1,19 @@ +-- External OpenID login +CREATE TABLE openidprovider ( + id bigserial PRIMARY KEY, + "name" text NOT NULL, + "base_url" text NOT NULL, + "client_id" text NOT NULL, + "client_secret" text NOT NULL, + "enabled" boolean NOT NULL DEFAULT FALSE, + CONSTRAINT openidprovider_name_unique UNIQUE ("name"), + CONSTRAINT openidprovider_client_id_unique UNIQUE ("client_id"), + CONSTRAINT openidprovider_client_secret_unique UNIQUE ("client_secret") +); + +ALTER TABLE "user" ADD COLUMN "openid_login" BOOLEAN NOT NULL DEFAULT FALSE; +ALTER TABLE settings ADD COLUMN openid_create_account BOOLEAN NOT NULL DEFAULT TRUE; + +-- Make emails unique +-- This migration may fail if there are duplicate emails in the database already +ALTER TABLE "user" ADD CONSTRAINT "user_email_key" UNIQUE (email); diff --git a/migrations/20240809083720_add_licenses.down.sql b/migrations/20240809083720_add_licenses.down.sql new file mode 100644 index 0000000000..4da11f6953 --- /dev/null +++ b/migrations/20240809083720_add_licenses.down.sql @@ -0,0 +1 @@ +ALTER TABLE settings DROP COLUMN license; diff --git a/migrations/20240809083720_add_licenses.up.sql b/migrations/20240809083720_add_licenses.up.sql new file mode 100644 index 0000000000..bf2fe97e7b --- /dev/null +++ b/migrations/20240809083720_add_licenses.up.sql @@ -0,0 +1 @@ +ALTER TABLE settings ADD COLUMN license TEXT DEFAULT NULL; diff --git a/migrations/20240819134151_enterprise_settings.down.sql b/migrations/20240819134151_enterprise_settings.down.sql new file mode 100644 index 0000000000..525e164fdf --- /dev/null +++ b/migrations/20240819134151_enterprise_settings.down.sql @@ -0,0 +1 @@ +DROP TABLE enterprisesettings; diff --git a/migrations/20240819134151_enterprise_settings.up.sql b/migrations/20240819134151_enterprise_settings.up.sql new file mode 100644 index 0000000000..4db6f235ef --- /dev/null +++ b/migrations/20240819134151_enterprise_settings.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE enterprisesettings ( + id bigserial PRIMARY KEY, + admin_device_management BOOLEAN NOT NULL DEFAULT false +); + +INSERT INTO enterprisesettings (admin_device_management) values (false); diff --git a/migrations/20240830094910_wireguard_manual_setup_setting.down.sql b/migrations/20240830094910_wireguard_manual_setup_setting.down.sql new file mode 100644 index 0000000000..4db133f798 --- /dev/null +++ b/migrations/20240830094910_wireguard_manual_setup_setting.down.sql @@ -0,0 +1 @@ +ALTER TABLE enterprisesettings DROP COLUMN only_client_activation; diff --git a/migrations/20240830094910_wireguard_manual_setup_setting.up.sql b/migrations/20240830094910_wireguard_manual_setup_setting.up.sql new file mode 100644 index 0000000000..491044d258 --- /dev/null +++ b/migrations/20240830094910_wireguard_manual_setup_setting.up.sql @@ -0,0 +1 @@ +ALTER TABLE enterprisesettings ADD COLUMN only_client_activation BOOLEAN NOT NULL DEFAULT false; diff --git a/migrations/20240902103930_add_option_to_disable_all_traffic.down.sql b/migrations/20240902103930_add_option_to_disable_all_traffic.down.sql new file mode 100644 index 0000000000..d07635dbce --- /dev/null +++ b/migrations/20240902103930_add_option_to_disable_all_traffic.down.sql @@ -0,0 +1 @@ +ALTER TABLE enterprisesettings DROP COLUMN disable_all_traffic; diff --git a/migrations/20240902103930_add_option_to_disable_all_traffic.up.sql b/migrations/20240902103930_add_option_to_disable_all_traffic.up.sql new file mode 100644 index 0000000000..21de95022a --- /dev/null +++ b/migrations/20240902103930_add_option_to_disable_all_traffic.up.sql @@ -0,0 +1 @@ +ALTER TABLE enterprisesettings ADD COLUMN disable_all_traffic BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/migrations/20240906090729_polling_token.down.sql b/migrations/20240906090729_polling_token.down.sql new file mode 100644 index 0000000000..bb7a045ee9 --- /dev/null +++ b/migrations/20240906090729_polling_token.down.sql @@ -0,0 +1 @@ +DROP TABLE pollingtoken; diff --git a/migrations/20240906090729_polling_token.up.sql b/migrations/20240906090729_polling_token.up.sql new file mode 100644 index 0000000000..933a426f90 --- /dev/null +++ b/migrations/20240906090729_polling_token.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE pollingtoken ( + id bigserial PRIMARY KEY, + token TEXT NOT NULL, + device_id bigint NOT NULL, + created_at timestamp without time zone NOT NULL DEFAULT now(), + FOREIGN KEY(device_id) REFERENCES "device"(id) ON DELETE CASCADE +); diff --git a/migrations/20240909143350_add_openid_sub.down.sql b/migrations/20240909143350_add_openid_sub.down.sql new file mode 100644 index 0000000000..a440b1d58e --- /dev/null +++ b/migrations/20240909143350_add_openid_sub.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE "user" DROP COLUMN "openid_sub"; +ALTER TABLE "user" ADD COLUMN "openid_login" BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/migrations/20240909143350_add_openid_sub.up.sql b/migrations/20240909143350_add_openid_sub.up.sql new file mode 100644 index 0000000000..9cbf71c504 --- /dev/null +++ b/migrations/20240909143350_add_openid_sub.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE "user" DROP COLUMN "openid_login"; +ALTER TABLE "user" ADD COLUMN "openid_sub" TEXT DEFAULT NULL; diff --git a/model-derive/Cargo.toml b/model-derive/Cargo.toml index 46013af7f8..5c0e90ea96 100644 --- a/model-derive/Cargo.toml +++ b/model-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "model_derive" -version = "0.1.2" +version = "0.2.0" edition = "2021" [lib] diff --git a/model-derive/src/lib.rs b/model-derive/src/lib.rs index acff2d3329..abc6764f38 100644 --- a/model-derive/src/lib.rs +++ b/model-derive/src/lib.rs @@ -163,26 +163,46 @@ pub fn derive(input: TokenStream) -> TokenStream { None }); let update_args = insert_args.clone(); + // Struct fields without `id`. It is not possible to use `..self`: mismatched types. + let struct_fields = named.iter().filter_map(|field| { + if let Some(name) = &field.ident { + if name != "id" { + return Some(quote! { #name: self.#name }); + } + } + None + }); // queries - let all_query = format!("SELECT id \"id?\", {cs_aliased_fields} FROM \"{table_name}\""); + let all_query = format!("SELECT id, {cs_aliased_fields} FROM \"{table_name}\""); let find_by_id_query = - format!("SELECT id \"id?\", {cs_aliased_fields} FROM \"{table_name}\" WHERE id = $1"); + format!("SELECT id, {cs_aliased_fields} FROM \"{table_name}\" WHERE id = $1"); let delete_query = format!("DELETE FROM \"{table_name}\" WHERE id = $1"); let insert_query = format!("INSERT INTO \"{table_name}\" ({cs_fields}) VALUES ({cs_values}) RETURNING id"); let update_query = format!("UPDATE \"{table_name}\" SET {cs_setters} WHERE id = $1"); + // TODO: add limit and offset for all(). quote! { - impl #name { - pub async fn find_by_id<'e, E>(executor: E, id: i64) -> Result, sqlx::Error> + impl #name { + pub async fn save<'e, E>(self, executor: E) -> Result<#name, sqlx::Error> + where + E: sqlx::PgExecutor<'e> + { + let id = sqlx::query_scalar!(#insert_query, #(#insert_args,)*).fetch_one(executor).await?; + + Ok(#name { id, #(#struct_fields,)* }) + } + } + + impl #name { + pub async fn find_by_id<'e, E>(executor: E, id: Id) -> Result, sqlx::Error> where E: sqlx::PgExecutor<'e> { sqlx::query_as!(Self, #find_by_id_query, id).fetch_optional(executor).await } - // TODO: add limit and offset pub async fn all<'e, E>(executor: E) -> Result, sqlx::Error> where E: sqlx::PgExecutor<'e> @@ -194,9 +214,8 @@ pub fn derive(input: TokenStream) -> TokenStream { where E: sqlx::PgExecutor<'e> { - if let Some(id) = self.id { - sqlx::query!(#delete_query, id).execute(executor).await?; - } + sqlx::query!(#delete_query, self.id).execute(executor).await?; + Ok(()) } @@ -204,15 +223,8 @@ pub fn derive(input: TokenStream) -> TokenStream { where E: sqlx::PgExecutor<'e> { - match self.id { - None => { - let id = sqlx::query_scalar!(#insert_query, #(#insert_args,)*).fetch_one(executor).await?; - self.id = Some(id); - } - Some(id) => { - sqlx::query!(#update_query, id, #(#update_args,)*).execute(executor).await?; - } - } + sqlx::query!(#update_query, self.id, #(#update_args,)*).execute(executor).await?; + Ok(()) } } diff --git a/proto b/proto index c71f378472..8309982b94 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit c71f37847279ee23220fcf9e0e45d2c365b3b8ee +Subproject commit 8309982b94e82a7cbe39dd529967f43e49b3ef1d diff --git a/src/appstate.rs b/src/appstate.rs index 6a772c47ce..78556a7566 100644 --- a/src/appstate.rs +++ b/src/appstate.rs @@ -5,6 +5,7 @@ use axum_extra::extract::cookie::Key; use reqwest::Client; use secrecy::ExposeSecret; use serde_json::json; +use sqlx::PgPool; use tokio::{ sync::{ broadcast::Sender, @@ -17,14 +18,14 @@ use webauthn_rs::prelude::*; use crate::{ auth::failed_login::FailedLoginMap, - db::{AppEvent, DbPool, GatewayEvent, WebHook}, + db::{AppEvent, GatewayEvent, WebHook}, mail::Mail, server_config, }; #[derive(Clone)] pub struct AppState { - pub pool: DbPool, + pub pool: PgPool, tx: UnboundedSender, wireguard_tx: Sender, pub mail_tx: UnboundedSender, @@ -44,7 +45,7 @@ impl AppState { } /// Handle webhook events - async fn handle_triggers(pool: DbPool, mut rx: UnboundedReceiver) { + async fn handle_triggers(pool: PgPool, mut rx: UnboundedReceiver) { let reqwest_client = Client::builder().user_agent("reqwest").build().unwrap(); while let Some(msg) = rx.recv().await { debug!("WebHook triggered"); @@ -97,7 +98,7 @@ impl AppState { /// Create application state pub fn new( - pool: DbPool, + pool: PgPool, tx: UnboundedSender, rx: UnboundedReceiver, wireguard_tx: Sender, diff --git a/src/auth/mod.rs b/src/auth/mod.rs index c5ea796f22..d1d0d0336d 100644 --- a/src/auth/mod.rs +++ b/src/auth/mod.rs @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; use crate::{ appstate::AppState, - db::{Group, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User}, + db::{Group, Id, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User}, error::WebError, handlers::SESSION_COOKIE_NAME, server_config, @@ -29,6 +29,8 @@ pub static AUTH_SECRET_ENV: &str = "DEFGUARD_AUTH_SECRET"; pub static GATEWAY_SECRET_ENV: &str = "DEFGUARD_GATEWAY_SECRET"; pub static YUBIBRIDGE_SECRET_ENV: &str = "DEFGUARD_YUBIBRIDGE_SECRET"; pub const TOTP_CODE_VALIDITY_PERIOD: u64 = 30; +pub const EMAIL_CODE_DIGITS: u32 = 6; +pub const TOTP_CODE_DIGITS: u32 = 6; #[derive(Clone, Copy, Default)] pub enum ClaimsType { @@ -150,14 +152,14 @@ where // This represents a session for a user who completed the login process (including MFA, if enabled). pub struct SessionInfo { pub session: Session, - pub user: User, + pub user: User, pub is_admin: bool, - groups: Vec, + groups: Vec>, } impl SessionInfo { #[must_use] - pub fn new(session: Session, user: User, is_admin: bool) -> Self { + pub fn new(session: Session, user: User, is_admin: bool) -> Self { Self { session, user, @@ -238,7 +240,7 @@ role!(UserAdminRole, admin_groupname useradmin_groupname); role!(VpnRole, admin_groupname vpn_groupname); // User authenticated by a valid access token -pub struct AccessUserInfo(pub(crate) User); +pub struct AccessUserInfo(pub(crate) User); #[async_trait] impl FromRequestParts for AccessUserInfo diff --git a/src/bin/defguard.rs b/src/bin/defguard.rs index 2e1d9e64f9..a25f50a836 100644 --- a/src/bin/defguard.rs +++ b/src/bin/defguard.rs @@ -3,14 +3,11 @@ use std::{ sync::{Arc, Mutex}, }; -use secrecy::ExposeSecret; -use tokio::sync::{broadcast, mpsc::unbounded_channel}; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - use defguard::{ auth::failed_login::FailedLoginMap, config::{Command, DefGuardConfig}, db::{init_db, AppEvent, GatewayEvent, Settings, User}, + enterprise::license::{run_periodic_license_check, set_cached_license, License}, grpc::{run_grpc_bidi_stream, run_grpc_server, GatewayMap, WorkerState}, headers::create_user_agent_parser, init_dev_env, init_vpn_location, @@ -18,8 +15,11 @@ use defguard::{ run_web_server, wireguard_peer_disconnect::run_periodic_peer_disconnect, wireguard_stats_purge::run_periodic_stats_purge, - SERVER_CONFIG, + SERVER_CONFIG, VERSION, }; +use secrecy::ExposeSecret; +use tokio::sync::{broadcast, mpsc::unbounded_channel}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[macro_use] extern crate tracing; @@ -34,18 +34,13 @@ async fn main() -> Result<(), anyhow::Error> { // initialize tracing tracing_subscriber::registry() .with( - tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { - format!( - "defguard={},tower_http=info,axum::rejection=trace", - config.log_level - ) - .into() - }), + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| config.log_level.clone().into()), ) .with(tracing_subscriber::fmt::layer()) .init(); - info!("Starting defguard"); + info!("Starting ... version v{}", VERSION); debug!("Using config: {config:?}"); let pool = init_db( @@ -106,6 +101,17 @@ async fn main() -> Result<(), anyhow::Error> { let failed_logins = FailedLoginMap::new(); let failed_logins = Arc::new(Mutex::new(failed_logins)); + debug!("Checking enterprise license status"); + match License::load_or_renew(&pool).await { + Ok(license) => { + set_cached_license(license); + } + Err(err) => { + warn!("There was an error while loading the license, error: {err}. The enterprise features will be disabled."); + set_cached_license(None); + } + }; + // run services tokio::select! { res = run_grpc_bidi_stream(pool.clone(), wireguard_tx.clone(), mail_tx.clone(), user_agent_parser.clone()), if config.proxy_url.is_some() => error!("Proxy gRPC stream returned early: {res:#?}"), @@ -113,7 +119,8 @@ async fn main() -> Result<(), anyhow::Error> { res = run_web_server(worker_state, gateway_state, webhook_tx, webhook_rx, wireguard_tx.clone(), mail_tx, pool.clone(), user_agent_parser, failed_logins) => error!("Web server returned early: {res:#?}"), res = run_mail_handler(mail_rx, pool.clone()) => error!("Mail handler returned early: {res:#?}"), res = run_periodic_peer_disconnect(pool.clone(), wireguard_tx) => error!("Periodic peer disconnect task returned early: {res:#?}"), - res = run_periodic_stats_purge(pool, config.stats_purge_frequency.into(), config.stats_purge_threshold.into()), if !config.disable_stats_purge => error!("Periodic stats purge task returned early: {res:#?}"), + res = run_periodic_stats_purge(pool.clone(), config.stats_purge_frequency.into(), config.stats_purge_threshold.into()), if !config.disable_stats_purge => error!("Periodic stats purge task returned early: {res:#?}"), + res = run_periodic_license_check(pool) => error!("Periodic license check task returned early: {res:#?}"), } Ok(()) } diff --git a/src/db/mod.rs b/src/db/mod.rs index 3b79ccd7b2..c444023abf 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,11 +1,13 @@ pub mod models; -use sqlx::postgres::PgConnectOptions; +use sqlx::postgres::{PgConnectOptions, PgPool}; -pub type DbPool = sqlx::postgres::PgPool; +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct NoId; +pub type Id = i64; /// Initializes and migrates postgres database. Returns DB pool object. -pub async fn init_db(host: &str, port: u16, name: &str, user: &str, password: &str) -> DbPool { +pub async fn init_db(host: &str, port: u16, name: &str, user: &str, password: &str) -> PgPool { info!("Initializing DB pool"); let opts = PgConnectOptions::new() .host(host) @@ -13,7 +15,7 @@ pub async fn init_db(host: &str, port: u16, name: &str, user: &str, password: &s .username(user) .password(password) .database(name); - let pool = DbPool::connect_with(opts) + let pool = PgPool::connect_with(opts) .await .expect("Database connection failed"); sqlx::migrate!() diff --git a/src/db/models/auth_code.rs b/src/db/models/auth_code.rs index f25aa9066b..736ae6092f 100644 --- a/src/db/models/auth_code.rs +++ b/src/db/models/auth_code.rs @@ -1,14 +1,17 @@ -use super::DbPool; -use crate::random::gen_alphanumeric; use chrono::Utc; use model_derive::Model; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; + +use crate::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; #[derive(Model, Clone)] #[table(authorization_code)] -pub struct AuthCode { - id: Option, - pub user_id: i64, +pub struct AuthCode { + id: I, + pub user_id: Id, pub client_id: String, pub code: String, pub redirect_uri: String, @@ -21,7 +24,7 @@ pub struct AuthCode { impl AuthCode { #[must_use] pub fn new( - user_id: i64, + user_id: Id, client_id: String, redirect_uri: String, scope: String, @@ -30,7 +33,7 @@ impl AuthCode { ) -> Self { let code = gen_alphanumeric(24); Self { - id: None, + id: NoId, user_id, client_id, code, @@ -41,12 +44,14 @@ impl AuthCode { code_challenge, } } +} +impl AuthCode { /// Find by code. - pub async fn find_code(pool: &DbPool, code: &str) -> Result, SqlxError> { + pub async fn find_code(pool: &PgPool, code: &str) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", user_id, client_id, code, redirect_uri, scope, auth_time, nonce, \ + "SELECT id, user_id, client_id, code, redirect_uri, scope, auth_time, nonce, \ code_challenge FROM authorization_code WHERE code = $1", code ) @@ -55,7 +60,7 @@ impl AuthCode { } // Remove a used authorization_code - pub async fn consume(self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn consume(self, pool: &PgPool) -> Result<(), SqlxError> { self.delete(pool).await } } diff --git a/src/db/models/authentication_key.rs b/src/db/models/authentication_key.rs index 4eb95c35f9..2bddb9a655 100644 --- a/src/db/models/authentication_key.rs +++ b/src/db/models/authentication_key.rs @@ -1,6 +1,8 @@ use model_derive::Model; use sqlx::{query_as, Error as SqlxError, PgExecutor, Type}; +use crate::db::{Id, NoId}; + #[derive(Clone, Debug, Deserialize, Serialize, Type)] #[sqlx(type_name = "authentication_key_type", rename_all = "lowercase")] #[serde(rename_all = "lowercase")] @@ -11,11 +13,11 @@ pub(crate) enum AuthenticationKeyType { #[derive(Deserialize, Model, Serialize)] #[table(authentication_key)] -pub(crate) struct AuthenticationKey { - id: Option, +pub(crate) struct AuthenticationKey { + id: I, pub yubikey_id: Option, pub name: Option, - pub user_id: i64, + pub user_id: Id, pub key: String, #[model(enum)] key_type: AuthenticationKeyType, @@ -24,14 +26,14 @@ pub(crate) struct AuthenticationKey { impl AuthenticationKey { #[must_use] pub fn new( - user_id: i64, + user_id: Id, key: String, name: Option, key_type: AuthenticationKeyType, yubikey_id: Option, ) -> Self { Self { - id: None, + id: NoId, yubikey_id, user_id, key, @@ -39,10 +41,12 @@ impl AuthenticationKey { key_type, } } +} +impl AuthenticationKey { pub async fn find_by_user_id<'e, E>( executor: E, - user_id: i64, + user_id: Id, key_type: Option, ) -> Result, SqlxError> where @@ -52,7 +56,7 @@ impl AuthenticationKey { Some(key_type) => { query_as!( Self, - "SELECT id \"id?\", user_id, yubikey_id \"yubikey_id?\", key, \ + "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, \ name, key_type \"key_type: AuthenticationKeyType\" \ FROM authentication_key WHERE user_id = $1 AND key_type = $2", user_id, @@ -64,7 +68,7 @@ impl AuthenticationKey { None => { query_as!( Self, - "SELECT id \"id?\", user_id, yubikey_id \"yubikey_id?\", key, \ + "SELECT id, user_id, yubikey_id \"yubikey_id?\", key, \ name, key_type \"key_type: AuthenticationKeyType\" \ FROM authentication_key WHERE user_id = $1", user_id diff --git a/src/db/models/device.rs b/src/db/models/device.rs index 62583415e8..6dd9028d7b 100644 --- a/src/db/models/device.rs +++ b/src/db/models/device.rs @@ -1,25 +1,25 @@ -use std::{ - fmt::{Display, Formatter}, - net::IpAddr, -}; +use std::{fmt, net::IpAddr}; use base64::{prelude::BASE64_STANDARD, Engine}; use chrono::{NaiveDateTime, Utc}; use ipnetwork::IpNetwork; use model_derive::Model; -use sqlx::{query, query_as, Error as SqlxError, FromRow, PgConnection, PgExecutor}; +use sqlx::{query, query_as, Error as SqlxError, FromRow, PgConnection, PgExecutor, PgPool}; use thiserror::Error; +use utoipa::ToSchema; use super::{ error::ModelError, wireguard::{WireguardNetwork, WIREGUARD_MAX_HANDSHAKE_MINUTES}, - DbPool, }; -use crate::KEY_LENGTH; +use crate::{ + db::{Id, NoId}, + KEY_LENGTH, +}; #[derive(Serialize)] pub struct DeviceConfig { - pub(crate) network_id: i64, + pub(crate) network_id: Id, pub(crate) network_name: String, pub(crate) config: String, pub(crate) address: IpAddr, @@ -31,35 +31,38 @@ pub struct DeviceConfig { pub(crate) keepalive_interval: i32, } -#[derive(Clone, Deserialize, Model, Serialize, Debug)] -pub struct Device { - pub id: Option, +#[derive(Clone, Deserialize, Model, Serialize, Debug, ToSchema)] +pub struct Device { + pub id: I, pub name: String, pub wireguard_pubkey: String, - pub user_id: i64, + pub user_id: Id, pub created: NaiveDateTime, } -impl Display for Device { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.id { - Some(device_id) => write!(f, "[ID {device_id}] {}", self.name), - None => write!(f, "{}", self.name), - } +impl fmt::Display for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl fmt::Display for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ID {}] {}", self.id, self.name) } } // helper struct which includes network configurations for a given device -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct DeviceInfo { #[serde(flatten)] - pub device: Device, + pub device: Device, pub network_info: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct DeviceNetworkInfo { - pub network_id: i64, + pub network_id: Id, pub device_wireguard_ip: IpAddr, #[serde(skip_serializing)] pub preshared_key: Option, @@ -67,18 +70,17 @@ pub struct DeviceNetworkInfo { } impl DeviceInfo { - pub async fn from_device<'e, E>(executor: E, device: Device) -> Result + pub async fn from_device<'e, E>(executor: E, device: Device) -> Result where E: PgExecutor<'e>, { debug!("Generating device info for {device}"); - let device_id = device.get_id()?; let network_info = query_as!( DeviceNetworkInfo, - "SELECT wireguard_network_id as network_id, wireguard_ip as \"device_wireguard_ip: IpAddr\", preshared_key, is_authorized \ + "SELECT wireguard_network_id network_id, wireguard_ip \"device_wireguard_ip: IpAddr\", preshared_key, is_authorized \ FROM wireguard_network_device \ WHERE device_id = $1", - device_id + device.id ) .fetch_all(executor) .await?; @@ -92,16 +94,16 @@ impl DeviceInfo { // helper struct which includes full device info // including network activity metadata -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct UserDevice { #[serde(flatten)] - pub device: Device, + pub device: Device, pub networks: Vec, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct UserDeviceNetworkInfo { - pub network_id: i64, + pub network_id: Id, pub network_name: String, pub network_gateway_ip: String, pub device_wireguard_ip: String, @@ -112,76 +114,74 @@ pub struct UserDeviceNetworkInfo { } impl UserDevice { - pub async fn from_device(pool: &DbPool, device: Device) -> Result, SqlxError> { - if let Some(device_id) = device.id { - // fetch device config and connection info for all networks - let result = query!( - "WITH stats AS ( \ - SELECT DISTINCT ON (network) network, endpoint, latest_handshake \ - FROM wireguard_peer_stats \ - WHERE device_id = $2 \ - ORDER BY network, collected_at DESC \ - ) \ - SELECT \ - n.id as network_id, n.name as network_name, n.endpoint as gateway_endpoint, \ - wnd.wireguard_ip as \"device_wireguard_ip: IpAddr\", stats.endpoint as device_endpoint, \ - stats.latest_handshake as \"latest_handshake?\", \ - COALESCE (((NOW() - stats.latest_handshake) < $1 * interval '1 minute'), false) as \"is_active!\" \ - FROM wireguard_network_device wnd \ - JOIN wireguard_network n ON n.id = wnd.wireguard_network_id \ - LEFT JOIN stats on n.id = stats.network \ - WHERE wnd.device_id = $2", - WIREGUARD_MAX_HANDSHAKE_MINUTES as f64, - device_id, - ) - .fetch_all(pool) - .await?; - - let networks_info: Vec = result - .into_iter() - .map(|r| { - let device_ip = match r.device_endpoint { - Some(endpoint) => endpoint.split(':').next().map(ToString::to_string), - None => None, - }; - UserDeviceNetworkInfo { - network_id: r.network_id, - network_name: r.network_name, - network_gateway_ip: r.gateway_endpoint, - device_wireguard_ip: r.device_wireguard_ip.to_string(), - last_connected_ip: device_ip, - last_connected_location: None, - last_connected_at: r.latest_handshake, - is_active: r.is_active, - } - }) - .collect(); - return Ok(Some(Self { - device, - networks: networks_info, - })); - } - Ok(None) + pub async fn from_device(pool: &PgPool, device: Device) -> Result, SqlxError> { + // fetch device config and connection info for all networks + let result = query!( + "WITH stats AS ( \ + SELECT DISTINCT ON (network) network, endpoint, latest_handshake \ + FROM wireguard_peer_stats \ + WHERE device_id = $2 \ + ORDER BY network, collected_at DESC \ + ) \ + SELECT \ + n.id network_id, n.name network_name, n.endpoint gateway_endpoint, \ + wnd.wireguard_ip \"device_wireguard_ip: IpAddr\", stats.endpoint device_endpoint, \ + stats.latest_handshake \"latest_handshake?\", \ + COALESCE (((NOW() - stats.latest_handshake) < $1 * interval '1 minute'), false) as \"is_active!\" \ + FROM wireguard_network_device wnd \ + JOIN wireguard_network n ON n.id = wnd.wireguard_network_id \ + LEFT JOIN stats on n.id = stats.network \ + WHERE wnd.device_id = $2", + WIREGUARD_MAX_HANDSHAKE_MINUTES as f64, + device.id, + ) + .fetch_all(pool) + .await?; + + let networks_info: Vec = result + .into_iter() + .map(|r| { + let device_ip = match r.device_endpoint { + Some(endpoint) => endpoint.split(':').next().map(ToString::to_string), + None => None, + }; + UserDeviceNetworkInfo { + network_id: r.network_id, + network_name: r.network_name, + network_gateway_ip: r.gateway_endpoint, + device_wireguard_ip: r.device_wireguard_ip.to_string(), + last_connected_ip: device_ip, + last_connected_location: None, + last_connected_at: r.latest_handshake, + is_active: r.is_active, + } + }) + .collect(); + + Ok(Some(Self { + device, + networks: networks_info, + })) } } -#[derive(Debug, Serialize, Deserialize, Clone, FromRow)] +#[derive(Clone, Debug, Deserialize, FromRow, Serialize)] pub struct WireguardNetworkDevice { - pub wireguard_network_id: i64, + pub wireguard_network_id: Id, pub wireguard_ip: IpAddr, - pub device_id: i64, + pub device_id: Id, pub preshared_key: Option, pub is_authorized: bool, pub authorized_at: Option, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Debug, Deserialize, Serialize, ToSchema)] pub struct AddDevice { pub name: String, pub wireguard_pubkey: String, } -#[derive(Deserialize, Debug)] +#[derive(Debug, Deserialize, ToSchema)] pub struct ModifyDevice { pub name: String, pub wireguard_pubkey: String, @@ -189,7 +189,7 @@ pub struct ModifyDevice { impl WireguardNetworkDevice { #[must_use] - pub fn new(network_id: i64, device_id: i64, wireguard_ip: IpAddr) -> Self { + pub fn new(network_id: Id, device_id: Id, wireguard_ip: IpAddr) -> Self { Self { wireguard_network_id: network_id, wireguard_ip, @@ -219,6 +219,7 @@ impl WireguardNetworkDevice { ) .execute(executor) .await?; + Ok(()) } @@ -239,6 +240,7 @@ impl WireguardNetworkDevice { ) .execute(executor) .await?; + Ok(()) } @@ -254,20 +256,21 @@ impl WireguardNetworkDevice { ) .execute(executor) .await?; + Ok(()) } pub async fn find<'e, E>( executor: E, - device_id: i64, - network_id: i64, + device_id: Id, + network_id: Id, ) -> Result, SqlxError> where E: PgExecutor<'e>, { let res = query_as!( Self, - "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ + "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ FROM wireguard_network_device \ WHERE device_id = $1 AND wireguard_network_id = $2", device_id, @@ -275,51 +278,52 @@ impl WireguardNetworkDevice { ) .fetch_optional(executor) .await?; + Ok(res) } pub async fn find_by_device( - pool: &DbPool, - device_id: i64, + pool: &PgPool, + device_id: Id, ) -> Result>, SqlxError> { let result = query_as!( Self, - "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ + "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ FROM wireguard_network_device WHERE device_id = $1", device_id ) .fetch_all(pool) .await?; - if !result.is_empty() { - return Ok(Some(result)); - } - Ok(None) + + Ok(if result.is_empty() { + None + } else { + Some(result) + }) } - pub async fn all_for_network<'e, E>( - executor: E, - network_id: i64, - ) -> Result, SqlxError> + pub async fn all_for_network<'e, E>(executor: E, network_id: Id) -> Result, SqlxError> where E: PgExecutor<'e>, { let res = query_as!( Self, - "SELECT device_id, wireguard_network_id, wireguard_ip as \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ + "SELECT device_id, wireguard_network_id, wireguard_ip \"wireguard_ip: IpAddr\", preshared_key, is_authorized, authorized_at \ FROM wireguard_network_device \ WHERE wireguard_network_id = $1", network_id ) .fetch_all(executor) .await?; + Ok(res) } } -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum DeviceError { #[error("Device {0} pubkey is the same as gateway pubkey for network {1}")] - PubkeyConflict(Device, String), + PubkeyConflict(Device, String), #[error("Database error")] DatabaseError(#[from] sqlx::Error), #[error("Model error")] @@ -330,30 +334,28 @@ pub enum DeviceError { impl Device { #[must_use] - pub fn new(name: String, wireguard_pubkey: String, user_id: i64) -> Self { + pub fn new(name: String, wireguard_pubkey: String, user_id: Id) -> Self { Self { - id: None, + id: NoId, name, wireguard_pubkey, user_id, created: Utc::now().naive_utc(), } } +} - pub fn get_id(&self) -> Result { - let id = self.id.ok_or(ModelError::IdNotSet)?; - Ok(id) - } - +impl Device { pub fn update_from(&mut self, other: ModifyDevice) { self.name = other.name; self.wireguard_pubkey = other.wireguard_pubkey; } - /// Create wireguard config for device + + /// Create WireGuard config for device. #[must_use] pub fn create_config( &self, - network: &WireguardNetwork, + network: &WireguardNetwork, wireguard_network_device: &WireguardNetworkDevice, ) -> String { let dns = match &network.dns { @@ -399,14 +401,14 @@ impl Device { pub async fn find_by_ip<'e, E>( executor: E, ip: IpAddr, - network_id: i64, + network_id: Id, ) -> Result, SqlxError> where E: PgExecutor<'e>, { query_as!( Self, - "SELECT d.id \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ + "SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ FROM device d \ JOIN wireguard_network_device wnd \ ON d.id = wnd.device_id \ @@ -424,7 +426,7 @@ impl Device { { query_as!( Self, - "SELECT id \"id?\", name, wireguard_pubkey, user_id, created \ + "SELECT id, name, wireguard_pubkey, user_id, created \ FROM device WHERE wireguard_pubkey = $1", pubkey ) @@ -433,13 +435,13 @@ impl Device { } pub async fn find_by_id_and_username( - pool: &DbPool, - id: i64, + pool: &PgPool, + id: Id, username: &str, ) -> Result, SqlxError> { query_as!( Self, - "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created \ + "SELECT device.id, name, wireguard_pubkey, user_id, created \ FROM device JOIN \"user\" ON device.user_id = \"user\".id \ WHERE device.id = $1 AND \"user\".username = $2", id, @@ -450,13 +452,13 @@ impl Device { } pub async fn find_by_id_and_user_id( - pool: &DbPool, - id: i64, - user_id: i64, + pool: &PgPool, + id: Id, + user_id: Id, ) -> Result, SqlxError> { query_as!( Self, - "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created \ + "SELECT device.id, name, wireguard_pubkey, user_id, created \ FROM device JOIN \"user\" ON device.user_id = \"user\".id \ WHERE device.id = $1 AND \"user\".id = $2", id, @@ -466,31 +468,24 @@ impl Device { .await } - pub async fn get_ip( - &self, - pool: &DbPool, - network_id: i64, - ) -> Result, SqlxError> { - if let Some(device_id) = self.id { - let result = query!( - "SELECT wireguard_ip \ - FROM wireguard_network_device \ - WHERE device_id = $1 AND wireguard_network_id = $2", - device_id, - network_id - ) - .fetch_one(pool) - .await?; - return Ok(Some(result.wireguard_ip.to_string())); - } + pub async fn get_ip(&self, pool: &PgPool, network_id: Id) -> Result, SqlxError> { + let result = query!( + "SELECT wireguard_ip \ + FROM wireguard_network_device \ + WHERE device_id = $1 AND wireguard_network_id = $2", + self.id, + network_id + ) + .fetch_one(pool) + .await?; - Ok(None) + Ok(Some(result.wireguard_ip.to_string())) } - pub async fn all_for_username(pool: &DbPool, username: &str) -> Result, SqlxError> { + pub async fn all_for_username(pool: &PgPool, username: &str) -> Result, SqlxError> { query_as!( Self, - "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created \ + "SELECT device.id, name, wireguard_pubkey, user_id, created \ FROM device JOIN \"user\" ON device.user_id = \"user\".id \ WHERE \"user\".username = $1", username @@ -518,18 +513,9 @@ impl Device { if network.pubkey == self.wireguard_pubkey { return Err(DeviceError::PubkeyConflict(self.clone(), network.name)); } - - let Some(network_id) = network.id else { - return Err(DeviceError::Unexpected("Network has no ID".to_string())); - }; - - if WireguardNetworkDevice::find( - &mut *transaction, - self.id.expect("Device has no ID"), - network_id, - ) - .await? - .is_some() + if WireguardNetworkDevice::find(&mut *transaction, self.id, network.id) + .await? + .is_some() { debug!("Device {self} already has an IP within network {network}. Skipping...",); continue; @@ -544,7 +530,7 @@ impl Device { wireguard_network_device.wireguard_ip, self.name, self.user_id ); let device_network_info = DeviceNetworkInfo { - network_id, + network_id: network.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key.clone(), is_authorized: wireguard_network_device.is_authorized, @@ -553,7 +539,7 @@ impl Device { let config = self.create_config(&network, &wireguard_network_device); configs.push(DeviceConfig { - network_id, + network_id: network.id, network_name: network.name, config, endpoint: format!("{}:{}", network.endpoint, network.port), @@ -573,12 +559,9 @@ impl Device { pub async fn assign_network_ip( &self, transaction: &mut PgConnection, - network: &WireguardNetwork, + network: &WireguardNetwork, reserved_ips: Option<&[IpAddr]>, ) -> Result { - let Some(network_id) = network.id else { - return Err(ModelError::CannotCreate); - }; let net_ip = network.address.ip(); let net_network = network.address.network(); let net_broadcast = network.address.broadcast(); @@ -593,13 +576,12 @@ impl Device { } // Break loop if IP is unassigned and return network device - if Self::find_by_ip(&mut *transaction, ip, network_id) + if Self::find_by_ip(&mut *transaction, ip, network.id) .await? .is_none() { info!("Created IP: {ip} for device: {}", self.name); - let wireguard_network_device = - WireguardNetworkDevice::new(network_id, self.get_id()?, ip); + let wireguard_network_device = WireguardNetworkDevice::new(network.id, self.id, ip); wireguard_network_device.insert(&mut *transaction).await?; return Ok(wireguard_network_device); } @@ -620,22 +602,20 @@ impl Device { #[cfg(test)] mod test { + use claims::{assert_err, assert_ok}; + use super::*; use crate::db::User; - use claims::{assert_err, assert_ok}; - impl Device { + impl Device { /// Create new device and assign IP in a given network pub async fn new_with_ip( - pool: &DbPool, - user_id: i64, + pool: &PgPool, + user_id: Id, name: String, pubkey: String, - network: &WireguardNetwork, + network: &WireguardNetwork, ) -> Result<(Self, WireguardNetworkDevice), ModelError> { - let Some(network_id) = network.id else { - return Err(ModelError::CannotCreate); - }; let net_ip = network.address.ip(); let net_network = network.address.network(); let net_broadcast = network.address.broadcast(); @@ -644,15 +624,19 @@ mod test { continue; } // Break loop if IP is unassigned and return device - if Self::find_by_ip(pool, ip, network_id).await?.is_none() { - let mut device = Self::new(name.clone(), pubkey, user_id); - device.save(pool).await?; + if Device::find_by_ip(pool, ip, network.id).await?.is_none() { + let device = Device::new(name.clone(), pubkey, user_id) + .save(pool) + .await?; info!("Created device: {}", device.name); debug!("For user: {}", device.user_id); let wireguard_network_device = - WireguardNetworkDevice::new(network_id, device.id.unwrap(), ip); + WireguardNetworkDevice::new(network.id, device.id, ip); wireguard_network_device.insert(pool).await?; - info!("Assigned IP: {ip} for device: {name} in network: {network_id}"); + info!( + "Assigned IP: {ip} for device: {name} in network: {}", + network.id + ); return Ok((device, wireguard_network_device)); } } @@ -661,29 +645,26 @@ mod test { } #[sqlx::test] - async fn test_assign_device_ip(pool: DbPool) { + async fn test_assign_device_ip(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/30").unwrap(); - network.save(&pool).await.unwrap(); + let network = network.save(&pool).await.unwrap(); - let mut user = User::new( + let user = User::new( "testuser", Some("hunter2"), "Tester", "Test", "test@test.com", None, - ); - user.save(&pool).await.unwrap(); - let (_device, wireguard_network_device) = Device::new_with_ip( - &pool, - user.id.unwrap(), - "dev1".into(), - "key1".into(), - &network, ) + .save(&pool) .await .unwrap(); + let (_device, wireguard_network_device) = + Device::new_with_ip(&pool, user.id, "dev1".into(), "key1".into(), &network) + .await + .unwrap(); assert_eq!( wireguard_network_device.wireguard_ip.to_string(), "10.1.1.2" diff --git a/src/db/models/device_login.rs b/src/db/models/device_login.rs index 39ca244331..5cbc371b88 100644 --- a/src/db/models/device_login.rs +++ b/src/db/models/device_login.rs @@ -1,16 +1,16 @@ -use std::fmt::{self, Display, Formatter}; +use std::fmt; use chrono::{NaiveDateTime, Utc}; use model_derive::Model; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; -use crate::db::DbPool; +use crate::db::{Id, NoId}; #[derive(Clone, Deserialize, Model, Serialize, Debug)] #[table(device_login_event)] -pub struct DeviceLoginEvent { - id: Option, - pub user_id: i64, +pub struct DeviceLoginEvent { + id: I, + pub user_id: Id, pub ip_address: String, pub model: Option, pub family: String, @@ -21,19 +21,22 @@ pub struct DeviceLoginEvent { pub created: NaiveDateTime, } -impl Display for DeviceLoginEvent { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self.id { - Some(device_id) => write!(f, "[ID {}] {}", device_id, self.family), - None => write!(f, "{}", self.family), - } +impl fmt::Display for DeviceLoginEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.family) + } +} + +impl fmt::Display for DeviceLoginEvent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ID {}] {}", self.id, self.family) } } impl DeviceLoginEvent { #[must_use] pub fn new( - user_id: i64, + user_id: Id, ip_address: String, model: Option, family: String, @@ -43,7 +46,7 @@ impl DeviceLoginEvent { event_type: String, ) -> Self { Self { - id: None, + id: NoId, user_id, ip_address, model, @@ -56,29 +59,31 @@ impl DeviceLoginEvent { } } - pub async fn find_device_login_event(&self, pool: &DbPool) -> Result, SqlxError> { - query_as!( - Self, - "SELECT id \"id?\", user_id, ip_address, model, family, brand, os_family, browser, event_type, created - FROM device_login_event WHERE user_id = $1 AND event_type = $2 AND family = $3 AND \ - brand = $4 AND model = $5 AND browser = $6", - self.user_id, self.event_type, self.family, self.brand, self.model, self.browser - ) - .fetch_optional(pool) - .await - } - - pub async fn check_if_device_already_logged_in( - mut self, - pool: &DbPool, - ) -> Result, anyhow::Error> { + pub(crate) async fn check_if_device_already_logged_in( + self, + pool: &PgPool, + ) -> Result>, anyhow::Error> { let existing_login_event = self.find_device_login_event(pool).await?; if existing_login_event.is_none() { - self.save(pool).await?; - Ok(Some(self)) + Ok(Some(self.save(pool).await?)) } else { Ok(None) } } + + pub async fn find_device_login_event( + &self, + pool: &PgPool, + ) -> Result>, SqlxError> { + query_as!( + DeviceLoginEvent::, + "SELECT id, user_id, ip_address, model, family, brand, os_family, browser, event_type, created \ + FROM device_login_event WHERE user_id = $1 AND event_type = $2 AND family = $3 AND \ + brand = $4 AND model = $5 AND browser = $6", + self.user_id, self.event_type, self.family, self.brand, self.model, self.browser + ) + .fetch_optional(pool) + .await + } } diff --git a/src/db/models/enrollment.rs b/src/db/models/enrollment.rs index 40d4f10719..c4b871adb4 100644 --- a/src/db/models/enrollment.rs +++ b/src/db/models/enrollment.rs @@ -1,13 +1,14 @@ use chrono::{Duration, NaiveDateTime, Utc}; use reqwest::Url; -use sqlx::{query, query_as, Error as SqlxError, PgConnection, PgExecutor}; +use sqlx::{query, query_as, Error as SqlxError, PgConnection, PgExecutor, PgPool}; use tera::{Context, Tera}; use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; use tonic::{Code, Status}; -use super::{settings::Settings, DbPool, User}; +use super::{settings::Settings, User}; use crate::{ + db::Id, mail::Mail, random::gen_alphanumeric, server_config, @@ -66,21 +67,21 @@ impl From for Status { | TokenError::WelcomeEmailNotConfigured | TokenError::TemplateError(_) | TokenError::TemplateErrorInternal(_) => (Code::Internal, "unexpected error"), - TokenError::NotFound - | TokenError::TokenExpired - | TokenError::SessionExpired - | TokenError::TokenUsed => (Code::Unauthenticated, "invalid token"), + TokenError::NotFound | TokenError::SessionExpired | TokenError::TokenUsed => { + (Code::Unauthenticated, "invalid token") + } TokenError::AlreadyActive => (Code::InvalidArgument, "already active"), + TokenError::TokenExpired => (Code::Unauthenticated, "token expired"), }; Status::new(code, msg) } } // Representation of a user enrollment session -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Token { pub id: String, - pub user_id: i64, + pub user_id: Id, pub admin_id: Option, pub email: Option, pub created_at: NaiveDateTime, @@ -92,8 +93,8 @@ pub struct Token { impl Token { #[must_use] pub fn new( - user_id: i64, - admin_id: Option, + user_id: Id, + admin_id: Option, email: Option, token_timeout_seconds: u64, token_type: Option, @@ -161,16 +162,22 @@ impl Token { session_timeout_seconds: u64, ) -> Result { // check if token can be used + debug!("Creating a new session."); if self.is_expired() { + debug!("Token is already expired. Cannot establish a new session."); return Err(TokenError::TokenExpired); } match self.used_at { // session started but still valid Some(used_at) if self.is_session_valid(session_timeout_seconds) => { + debug!("Session already exists yet it is still valid."); Ok(used_at + Duration::seconds(session_timeout_seconds as i64)) } // session expired - Some(_) => Err(TokenError::TokenUsed), + Some(_) => { + debug!("Session has expired."); + Err(TokenError::TokenUsed) + } // session not yet started None => { let now = Utc::now().naive_utc(); @@ -179,13 +186,14 @@ impl Token { .await?; self.used_at = Some(now); + debug!("Generate a new session successfully."); Ok(now + Duration::seconds(session_timeout_seconds as i64)) } } } - pub async fn find_by_id(pool: &DbPool, id: &str) -> Result { - match query_as!( + pub async fn find_by_id(pool: &PgPool, id: &str) -> Result { + if let Some(enrollment) = query_as!( Self, "SELECT id, user_id, admin_id, email, created_at, expires_at, used_at, token_type \ FROM token WHERE id = $1", @@ -194,12 +202,15 @@ impl Token { .fetch_optional(pool) .await? { - Some(enrollment) => Ok(enrollment), - None => Err(TokenError::NotFound), + debug!("Fetch token {enrollment:?} from database."); + Ok(enrollment) + } else { + debug!("Token with id {id} does not exist in database."); + Err(TokenError::NotFound) } } - pub async fn fetch_all(pool: &DbPool) -> Result, TokenError> { + pub async fn fetch_all(pool: &PgPool) -> Result, TokenError> { let tokens = query_as!( Self, "SELECT id, user_id, admin_id, email, created_at, expires_at, used_at, token_type \ @@ -210,38 +221,43 @@ impl Token { Ok(tokens) } - pub async fn fetch_user<'e, E>(&self, executor: E) -> Result + pub async fn fetch_user<'e, E>(&self, executor: E) -> Result, TokenError> where E: PgExecutor<'e>, { - debug!("Fetching user for enrollment"); + debug!("Find user by id {}.", self.user_id); let Some(user) = User::find_by_id(executor, self.user_id).await? else { error!("User not found for enrollment token {}", self.id); return Err(TokenError::UserNotFound); }; + debug!("Fetched user {user:?}."); + Ok(user) } - pub async fn fetch_admin<'e, E>(&self, executor: E) -> Result, TokenError> + pub async fn fetch_admin<'e, E>(&self, executor: E) -> Result>, TokenError> where E: PgExecutor<'e>, { - debug!("Fetching admin for enrollment"); + debug!("Fetch admin data."); if self.admin_id.is_none() { + debug!("Admin don't have id. Stop fetching data..."); return Ok(None); } let admin_id = self.admin_id.unwrap(); + debug!("Trying to find admin using id {admin_id}"); let user = User::find_by_id(executor, admin_id).await?; - info!("Fetched admin id {} for enrollment", admin_id); + debug!("Fetched admin {user:?}."); + Ok(user) } pub async fn delete_unused_user_tokens( transaction: &mut PgConnection, - user_id: i64, + user_id: Id, ) -> Result<(), TokenError> { - debug!("Deleting unused enrollment tokens for user {user_id}"); + debug!("Deleting unused tokens for the user."); let result = query!( "DELETE FROM token \ WHERE user_id = $1 \ @@ -251,7 +267,7 @@ impl Token { .execute(transaction) .await?; info!( - "Deleted {} unused enrollment tokens for user {user_id}", + "Deleted {} unused enrollment tokens for the user.", result.rows_affected() ); @@ -260,7 +276,7 @@ impl Token { pub async fn delete_unused_user_password_reset_tokens( transaction: &mut PgConnection, - user_id: i64, + user_id: Id, ) -> Result<(), TokenError> { debug!("Deleting unused password reset tokens for user {user_id}"); let result = query!( @@ -361,14 +377,14 @@ impl Token { } } -impl User { +impl User { /// Start user enrollment process /// This creates a new enrollment token valid for 24h /// and optionally sends enrollment email notification to user pub async fn start_enrollment( &self, transaction: &mut PgConnection, - admin: &User, + admin: &User, email: Option, token_timeout_seconds: u64, enrollment_service_url: Url, @@ -376,13 +392,23 @@ impl User { mail_tx: UnboundedSender, ) -> Result { info!( - "User {} starting enrollment for user {}, notification enabled: {send_user_notification}", + "User {} started a new enrollment process for user {}.", admin.username, self.username ); + debug!( + "Notify user by mail about the enrollment process: {}", + send_user_notification + ); + debug!("Check if {} has a password.", self.username); if self.has_password() { + debug!( + "User {} that you want to start enrollment process for already has a password.", + self.username + ); return Err(TokenError::AlreadyActive); } + debug!("Verify that {} is an active user.", self.username); if !self.is_active { warn!( "Can't create enrollment token for disabled user {}", @@ -391,25 +417,28 @@ impl User { return Err(TokenError::UserDisabled); } - let user_id = self.id.expect("User without ID"); - let admin_id = admin.id.expect("Admin user without ID"); - self.clear_unused_enrollment_tokens(&mut *transaction) .await?; + debug!("Create a new enrollment token for user {}.", self.username); let enrollment = Token::new( - user_id, - Some(admin_id), + self.id, + Some(admin.id), email.clone(), token_timeout_seconds, Some(ENROLLMENT_TOKEN_TYPE.to_string()), ); + debug!("Saving a new enrollment token..."); enrollment.save(&mut *transaction).await?; + debug!( + "Saved a new enrollment token with id {} for user {}.", + enrollment.id, self.username + ); if send_user_notification { if let Some(email) = email { debug!( - "Sending enrollment start mail for user {} to {email}", + "Sending an enrollment mail for user {} to {email}.", self.username ); let base_message_context = enrollment @@ -423,7 +452,14 @@ impl User { enrollment_service_url, &enrollment.id, ) - .map_err(|err| TokenError::NotificationError(err.to_string()))?, + .map_err(|err| { + debug!( + "Cannot send an email to the user {} due to the error {}.", + self.username, + err.to_string() + ); + TokenError::NotificationError(err.to_string()) + })?, attachments: Vec::new(), result_tx: None, }; @@ -441,16 +477,21 @@ impl User { } } } + info!( + "New enrollment token has been generated for {}.", + self.username + ); Ok(enrollment.id) } + /// Start user remote desktop configuration process /// This creates a new enrollment token valid for 24h /// and optionally sends email notification to user pub async fn start_remote_desktop_configuration( &self, transaction: &mut PgConnection, - admin: &User, + admin: &User, email: Option, token_timeout_seconds: u64, enrollment_service_url: Url, @@ -458,16 +499,18 @@ impl User { mail_tx: UnboundedSender, ) -> Result { info!( - "User {} starting desktop configuration for user {}, notification enabled: {send_user_notification}", + "User {} starting a new desktop activation for user {}", admin.username, self.username ); + debug!( + "Notify {} by mail about the enrollment process: {}", + self.username, send_user_notification + ); - let user_id = self.id.expect("User without ID"); - let admin_id = admin.id.expect("Admin user without ID"); - + debug!("Verify that {} is an active user.", self.username); if !self.is_active { warn!( - "Can't create desktop configuration enrollment token for disabled user {}", + "Can't create desktop activation token for disabled user {}.", self.username ); return Err(TokenError::UserDisabled); @@ -475,23 +518,33 @@ impl User { self.clear_unused_enrollment_tokens(&mut *transaction) .await?; + debug!("Cleared unused tokens for {}.", self.username); - let enrollment = Token::new( - user_id, - Some(admin_id), + debug!( + "Create a new desktop activation token for user {}.", + self.username + ); + let desktop_configuration = Token::new( + self.id, + Some(admin.id), email.clone(), token_timeout_seconds, Some(ENROLLMENT_TOKEN_TYPE.to_string()), ); - enrollment.save(&mut *transaction).await?; + debug!("Saving a new desktop configuration token..."); + desktop_configuration.save(&mut *transaction).await?; + debug!( + "Saved a new desktop activation token with id {} for user {}.", + desktop_configuration.id, self.username + ); if send_user_notification { if let Some(email) = email { debug!( - "Sending desktop configuration start mail for user {} to {email}", + "Sending a desktop configuration mail for user {} to {email}", self.username ); - let base_message_context = enrollment + let base_message_context = desktop_configuration .get_welcome_message_context(&mut *transaction) .await?; let mail = Mail { @@ -500,9 +553,16 @@ impl User { content: templates::desktop_start_mail( base_message_context, &enrollment_service_url, - &enrollment.id, + &desktop_configuration.id, ) - .map_err(|err| TokenError::NotificationError(err.to_string()))?, + .map_err(|err| { + debug!( + "Cannot send an email to the user {} due to the error {}.", + self.username, + err.to_string() + ); + TokenError::NotificationError(err.to_string()) + })?, attachments: Vec::new(), result_tx: None, }; @@ -519,8 +579,12 @@ impl User { } } } + info!( + "New desktop activation token has been generated for {}.", + self.username + ); - Ok(enrollment.id) + Ok(desktop_configuration.id) } // Remove unused tokens when triggering user enrollment @@ -528,25 +592,9 @@ impl User { &self, transaction: &mut PgConnection, ) -> Result<(), TokenError> { - info!( - "Removing unused enrollment tokens for user {}", - self.username - ); - Token::delete_unused_user_tokens(transaction, self.id.expect("Missing user ID")).await + info!("Removing unused tokens for user {}.", self.username); + Token::delete_unused_user_tokens(transaction, self.id).await } - - // pub async fn request_password_reset( - // &self, - // transaction: &mut PgConnection, - // admin: &User, - // // email: Option, - // token_timeout_seconds: u64, - // // enrollment_service_url: Url, - // // send_user_notification: bool, - // mail_tx: UnboundedSender, - // ) -> Result { - - // } } impl Settings { diff --git a/src/db/models/group.rs b/src/db/models/group.rs index 73f4476188..99ece048e5 100644 --- a/src/db/models/group.rs +++ b/src/db/models/group.rs @@ -1,14 +1,15 @@ use model_derive::Model; use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgConnection, PgExecutor}; +use utoipa::ToSchema; use crate::{ - db::{models::error::ModelError, User, WireguardNetwork}, + db::{models::error::ModelError, Id, NoId, User, WireguardNetwork}, server_config, }; -#[derive(Model, Debug)] -pub struct Group { - pub(crate) id: Option, +#[derive(Debug, Model, ToSchema)] +pub struct Group { + pub(crate) id: I, pub name: String, } @@ -16,61 +17,51 @@ impl Group { #[must_use] pub fn new>(name: S) -> Self { Self { - id: None, + id: NoId, name: name.into(), } } +} +impl Group { pub async fn find_by_name<'e, E>(executor: E, name: &str) -> Result, SqlxError> where E: PgExecutor<'e>, { - query_as!( - Self, - "SELECT id \"id?\", name FROM \"group\" WHERE name = $1", - name - ) - .fetch_optional(executor) - .await + query_as!(Self, "SELECT id, name FROM \"group\" WHERE name = $1", name) + .fetch_optional(executor) + .await } pub async fn member_usernames<'e, E>(&self, executor: E) -> Result, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_scalar!( - "SELECT \"user\".username FROM \"user\" JOIN group_user ON \"user\".id = group_user.user_id \ - WHERE group_user.group_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_scalar!( + "SELECT \"user\".username FROM \"user\" JOIN group_user ON \"user\".id = group_user.user_id \ + WHERE group_user.group_id = $1", + self.id + ) + .fetch_all(executor) + .await } - pub async fn members<'e, E>(&self, executor: E) -> Result, SqlxError> + pub async fn members<'e, E>(&self, executor: E) -> Result>, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - User, - "SELECT \"user\".id \"id?\", username, password_hash, last_name, first_name, email, \ - phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, \ - mfa_method \"mfa_method: _\", recovery_codes, is_active \ - FROM \"user\" \ - JOIN group_user ON \"user\".id = group_user.user_id \ - WHERE group_user.group_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + User, + "SELECT \"user\".id, username, password_hash, last_name, first_name, email, \ + phone, mfa_enabled, totp_enabled, totp_secret, email_mfa_enabled, email_mfa_secret, \ + mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ + FROM \"user\" \ + JOIN group_user ON \"user\".id = group_user.user_id \ + WHERE group_user.group_id = $1", + self.id + ) + .fetch_all(executor) + .await } /// Fetches a list of VPN locations where a given group is explicitly allowed. @@ -80,21 +71,17 @@ impl Group { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_scalar!( - "SELECT wn.name FROM wireguard_network wn JOIN wireguard_network_allowed_group wnag ON wn.id = wnag.network_id \ - WHERE wnag.group_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_scalar!( + "SELECT wn.name FROM wireguard_network wn JOIN wireguard_network_allowed_group wnag ON wn.id = wnag.network_id \ + WHERE wnag.group_id = $1", + self.id + ) + .fetch_all(executor) + .await } } -impl WireguardNetwork { +impl WireguardNetwork { /// Fetch a list of all allowed groups for a given network from DB pub async fn fetch_allowed_groups<'e, E>(&self, executor: E) -> Result, ModelError> where @@ -108,6 +95,7 @@ impl WireguardNetwork { ) .fetch_all(executor) .await?; + Ok(groups) } @@ -230,12 +218,11 @@ impl WireguardNetwork { #[cfg(test)] mod test { use super::*; - use crate::db::{DbPool, User}; + use crate::db::{PgPool, User}; #[sqlx::test] - async fn test_group(pool: DbPool) { - let mut group = Group::new("worker"); - group.save(&pool).await.unwrap(); + async fn test_group(pool: PgPool) { + let group = Group::new("worker").save(&pool).await.unwrap(); let fetched_group = Group::find_by_name(&pool, "worker").await.unwrap(); assert!(fetched_group.is_some()); @@ -251,19 +238,19 @@ mod test { } #[sqlx::test] - async fn test_group_members(pool: DbPool) { - let mut group = Group::new("worker"); - group.save(&pool).await.unwrap(); - - let mut user = User::new( + async fn test_group_members(pool: PgPool) { + let group = Group::new("worker").save(&pool).await.unwrap(); + let user = User::new( "hpotter", Some("pass123"), "Potter", "Harry", "h.potter@hogwart.edu.uk", None, - ); - user.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); user.add_to_group(&pool, &group).await.unwrap(); let members = group.member_usernames(&pool).await.unwrap(); diff --git a/src/db/models/mod.rs b/src/db/models/mod.rs index a814dcbcb6..d13db81b33 100644 --- a/src/db/models/mod.rs +++ b/src/db/models/mod.rs @@ -12,6 +12,7 @@ pub mod oauth2authorizedapp; pub mod oauth2client; #[cfg(feature = "openid")] pub mod oauth2token; +pub mod polling_token; pub mod session; pub mod settings; pub mod user; @@ -21,13 +22,14 @@ pub mod webhook; pub mod wireguard; pub mod yubikey; -use sqlx::{query_as, Error as SqlxError, PgConnection}; +use sqlx::{query_as, Error as SqlxError, PgConnection, PgPool}; +use utoipa::ToSchema; use self::{ device::UserDevice, user::{MFAMethod, User}, }; -use super::{DbPool, Group}; +use super::{Group, Id}; #[cfg(feature = "openid")] #[derive(Deserialize, Serialize)] @@ -38,32 +40,32 @@ pub struct NewOpenIDClient { pub enabled: bool, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Debug, Deserialize, Serialize)] pub struct WalletInfo { pub address: String, pub name: String, - pub chain_id: i64, + pub chain_id: Id, pub use_for_mfa: bool, } #[derive(Deserialize, Serialize, Debug, Clone)] pub struct OAuth2AuthorizedAppInfo { - pub oauth2client_id: i64, - pub user_id: i64, + pub oauth2client_id: Id, + pub user_id: Id, pub oauth2client_name: String, } /// Only `id` and `name` from [`WebAuthn`]. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Debug, Deserialize, Serialize)] pub struct SecurityKey { - pub id: i64, + pub id: Id, pub name: String, } // Basic user info used in user list, etc. -#[derive(Deserialize, Serialize, Debug, Clone)] +#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)] pub struct UserInfo { - pub id: Option, + pub id: Id, pub username: String, pub last_name: String, pub first_name: String, @@ -80,7 +82,7 @@ pub struct UserInfo { } impl UserInfo { - pub async fn from_user(pool: &DbPool, user: &User) -> Result { + pub async fn from_user(pool: &PgPool, user: &User) -> Result { let groups = user.member_of_names(pool).await?; let authorized_apps = user.oauth2authorizedapps(pool).await?; @@ -98,7 +100,7 @@ impl UserInfo { mfa_method: user.mfa_method.clone(), authorized_apps, is_active: user.is_active, - enrolled: user.has_password(), + enrolled: user.is_enrolled(), }) } @@ -109,7 +111,7 @@ impl UserInfo { pub(crate) async fn handle_status_change( &self, transaction: &mut PgConnection, - user: &mut User, + user: &mut User, ) -> Result { if self.is_active == user.is_active { Ok(false) @@ -129,7 +131,7 @@ impl UserInfo { pub(crate) async fn handle_user_groups( &mut self, transaction: &mut PgConnection, - user: &mut User, + user: &mut User, ) -> Result { // initialize return value let mut groups_changed = false; @@ -165,14 +167,15 @@ impl UserInfo { } /// Copy fields to [`User`]. This function is safe to call by a non-admin user. - pub fn into_user_safe_fields(self, user: &mut User) -> Result<(), SqlxError> { + pub fn into_user_safe_fields(self, user: &mut User) -> Result<(), SqlxError> { user.phone = self.phone; user.mfa_method = self.mfa_method; + Ok(()) } /// Copy fields to [`User`]. This function should be used by administrators. - pub fn into_user_all_fields(self, user: &mut User) -> Result<(), SqlxError> { + pub fn into_user_all_fields(self, user: &mut User) -> Result<(), SqlxError> { user.phone = self.phone; user.username = self.username; user.last_name = self.last_name; @@ -184,7 +187,7 @@ impl UserInfo { } // Full user info with related objects -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, ToSchema)] pub struct UserDetails { pub user: UserInfo, #[serde(default)] @@ -196,8 +199,8 @@ pub struct UserDetails { } impl UserDetails { - pub async fn from_user(pool: &DbPool, user: &User) -> Result { - let devices = user.devices(pool).await?; + pub async fn from_user(pool: &PgPool, user: &User) -> Result { + let devices = user.user_devices(pool).await?; let wallets = user.wallets(pool).await?; let security_keys = user.security_keys(pool).await?; @@ -220,19 +223,15 @@ pub struct MFAInfo { } impl MFAInfo { - pub async fn for_user(pool: &DbPool, user: &User) -> Result, SqlxError> { - if let Some(id) = user.id { - query_as!( - Self, - "SELECT mfa_method \"mfa_method: _\", totp_enabled totp_available, email_mfa_enabled email_available, \ - (SELECT count(*) > 0 FROM wallet WHERE user_id = $1 AND wallet.use_for_mfa) \"web3_available!\", \ - (SELECT count(*) > 0 FROM webauthn WHERE user_id = $1) \"webauthn_available!\" \ - FROM \"user\" WHERE \"user\".id = $1", - id - ).fetch_optional(pool).await - } else { - Ok(None) - } + pub async fn for_user(pool: &PgPool, user: &User) -> Result, SqlxError> { + query_as!( + Self, + "SELECT mfa_method \"mfa_method: _\", totp_enabled totp_available, email_mfa_enabled email_available, \ + (SELECT count(*) > 0 FROM wallet WHERE user_id = $1 AND wallet.use_for_mfa) \"web3_available!\", \ + (SELECT count(*) > 0 FROM webauthn WHERE user_id = $1) \"webauthn_available!\" \ + FROM \"user\" WHERE \"user\".id = $1", + user.id + ).fetch_optional(pool).await } #[must_use] @@ -276,25 +275,23 @@ mod test { use super::*; #[sqlx::test] - async fn test_user_info(pool: DbPool) { - let mut user = User::new( + async fn test_user_info(pool: PgPool) { + let user = User::new( "hpotter", Some("pass123"), "Potter", "Harry", "h.potter@hogwart.edu.uk", None, - ); - user.save(&pool).await.unwrap(); - - let mut group1 = Group::new("Gryffindor"); - group1.save(&pool).await.unwrap(); - let mut group2 = Group::new("Hufflepuff"); - group2.save(&pool).await.unwrap(); - let mut group3 = Group::new("Ravenclaw"); - group3.save(&pool).await.unwrap(); - let mut group4 = Group::new("Slytherin"); - group4.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); + + let group1 = Group::new("Gryffindor").save(&pool).await.unwrap(); + let group2 = Group::new("Hufflepuff").save(&pool).await.unwrap(); + let group3 = Group::new("Ravenclaw").save(&pool).await.unwrap(); + let group4 = Group::new("Slytherin").save(&pool).await.unwrap(); user.add_to_group(&pool, &group1).await.unwrap(); user.add_to_group(&pool, &group2).await.unwrap(); diff --git a/src/db/models/oauth2authorizedapp.rs b/src/db/models/oauth2authorizedapp.rs index 011221b93d..094d4738d8 100644 --- a/src/db/models/oauth2authorizedapp.rs +++ b/src/db/models/oauth2authorizedapp.rs @@ -1,31 +1,35 @@ -use super::DbPool; use model_derive::Model; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; + +use crate::db::{Id, NoId}; #[derive(Model)] -pub struct OAuth2AuthorizedApp { - pub id: Option, - pub user_id: i64, - pub oauth2client_id: i64, +pub struct OAuth2AuthorizedApp { + pub id: I, + pub user_id: Id, + pub oauth2client_id: Id, } impl OAuth2AuthorizedApp { #[must_use] - pub fn new(user_id: i64, oauth2client_id: i64) -> Self { + pub fn new(user_id: Id, oauth2client_id: Id) -> Self { Self { - id: None, + id: NoId, user_id, oauth2client_id, } } +} + +impl OAuth2AuthorizedApp { pub async fn find_by_user_and_oauth2client_id( - pool: &DbPool, - user_id: i64, - oauth2client_id: i64, + pool: &PgPool, + user_id: Id, + oauth2client_id: Id, ) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", user_id, oauth2client_id \ + "SELECT id, user_id, oauth2client_id \ FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = $2", user_id, oauth2client_id diff --git a/src/db/models/oauth2client.rs b/src/db/models/oauth2client.rs index db13edf321..530e73fffd 100644 --- a/src/db/models/oauth2client.rs +++ b/src/db/models/oauth2client.rs @@ -1,11 +1,15 @@ -use super::{DbPool, NewOpenIDClient}; -use crate::random::gen_alphanumeric; use model_derive::Model; -use sqlx::{query_as, Error as SqlxError}; +use sqlx::{query_as, Error as SqlxError, PgPool}; + +use super::NewOpenIDClient; +use crate::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; #[derive(Deserialize, Model, Serialize)] -pub struct OAuth2Client { - pub id: Option, +pub struct OAuth2Client { + pub id: I, pub client_id: String, // unique pub client_secret: String, #[model(ref)] @@ -23,7 +27,7 @@ impl OAuth2Client { let client_id = gen_alphanumeric(16); let client_secret = gen_alphanumeric(32); Self { - id: None, + id: NoId, client_id, client_secret, redirect_uri, @@ -38,7 +42,7 @@ impl OAuth2Client { let client_id = gen_alphanumeric(16); let client_secret = gen_alphanumeric(32); Self { - id: None, + id: NoId, client_id, client_secret, redirect_uri: new.redirect_uri, @@ -47,15 +51,17 @@ impl OAuth2Client { enabled: new.enabled, } } +} +impl OAuth2Client { /// Find client by 'client_id`. pub async fn find_by_client_id( - pool: &DbPool, + pool: &PgPool, client_id: &str, ) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled \ + "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled \ FROM oauth2client WHERE client_id = $1", client_id ) @@ -65,13 +71,13 @@ impl OAuth2Client { /// Find using `client_id` and `client_secret`; must be `enabled`. pub async fn find_by_auth( - pool: &DbPool, + pool: &PgPool, client_id: &str, client_secret: &str, ) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled \ + "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled \ FROM oauth2client WHERE client_id = $1 AND client_secret = $2 AND enabled", client_id, client_secret @@ -82,12 +88,12 @@ impl OAuth2Client { /// Find enabled client by `client_id`. pub async fn find_enabled_for_client_id( - pool: &DbPool, + pool: &PgPool, client_id: &str, ) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", client_id, client_secret, redirect_uri, scope, name, enabled \ + "SELECT id, client_id, client_secret, redirect_uri, scope, name, enabled \ FROM oauth2client WHERE client_id = $1 AND enabled", client_id ) @@ -104,8 +110,8 @@ pub struct OAuth2ClientSafe { pub scope: Vec, } -impl From for OAuth2ClientSafe { - fn from(client: OAuth2Client) -> Self { +impl From> for OAuth2ClientSafe { + fn from(client: OAuth2Client) -> Self { OAuth2ClientSafe { client_id: client.client_id, name: client.name, diff --git a/src/db/models/oauth2token.rs b/src/db/models/oauth2token.rs index a49a04ffa3..ad4b9624c6 100644 --- a/src/db/models/oauth2token.rs +++ b/src/db/models/oauth2token.rs @@ -1,10 +1,10 @@ -use super::DbPool; -use crate::{random::gen_alphanumeric, server_config}; use chrono::{Duration, Utc}; -use sqlx::{query, query_as, Error as SqlxError}; +use sqlx::{query, query_as, Error as SqlxError, PgPool}; + +use crate::{db::Id, random::gen_alphanumeric, server_config}; pub struct OAuth2Token { - pub oauth2authorizedapp_id: i64, + pub oauth2authorizedapp_id: Id, pub access_token: String, pub refresh_token: String, pub redirect_uri: String, @@ -14,7 +14,7 @@ pub struct OAuth2Token { impl OAuth2Token { #[must_use] - pub fn new(oauth2authorizedapp_id: i64, redirect_uri: String, scope: String) -> Self { + pub fn new(oauth2authorizedapp_id: Id, redirect_uri: String, scope: String) -> Self { let timeout = server_config().session_timeout; let expiration = Utc::now() + Duration::seconds(timeout.as_secs() as i64); Self { @@ -28,7 +28,7 @@ impl OAuth2Token { } /// Generate new access token, scratching the old one. Changes are reflected in the database. - pub async fn refresh_and_save(&mut self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn refresh_and_save(&mut self, pool: &PgPool) -> Result<(), SqlxError> { let timeout = server_config().session_timeout; let new_access_token = gen_alphanumeric(24); let new_refresh_token = gen_alphanumeric(24); @@ -55,7 +55,7 @@ impl OAuth2Token { } /// Store data in the database. - pub async fn save(&self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn save(&self, pool: &PgPool) -> Result<(), SqlxError> { query!( "INSERT INTO oauth2token (oauth2authorizedapp_id, access_token, refresh_token, redirect_uri, scope, expires_in) \ VALUES ($1, $2, $3, $4, $5, $6)", @@ -71,7 +71,7 @@ impl OAuth2Token { } /// Delete token from the database. - pub async fn delete(self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn delete(self, pool: &PgPool) -> Result<(), SqlxError> { query!( "DELETE FROM oauth2token WHERE access_token = $1 AND refresh_token = $2", self.access_token, @@ -84,7 +84,7 @@ impl OAuth2Token { /// Find by access token. pub async fn find_access_token( - pool: &DbPool, + pool: &PgPool, access_token: &str, ) -> Result, SqlxError> { match query_as!( @@ -111,7 +111,7 @@ impl OAuth2Token { /// Find by refresh token. pub async fn find_refresh_token( - pool: &DbPool, + pool: &PgPool, refresh_token: &str, ) -> Result, SqlxError> { match query_as!( @@ -137,8 +137,8 @@ impl OAuth2Token { } // Find by authorized app id pub async fn find_by_authorized_app_id( - pool: &DbPool, - oauth2authorizedapp_id: i64, + pool: &PgPool, + oauth2authorizedapp_id: Id, ) -> Result, SqlxError> { match query_as!( Self, diff --git a/src/db/models/polling_token.rs b/src/db/models/polling_token.rs new file mode 100644 index 0000000000..cd20e62ee8 --- /dev/null +++ b/src/db/models/polling_token.rs @@ -0,0 +1,52 @@ +use chrono::{NaiveDateTime, Utc}; +use model_derive::Model; +use sqlx::{query_as, Error as SqlxError, PgExecutor, PgPool}; + +use crate::{ + db::{Id, NoId}, + random::gen_alphanumeric, +}; + +// Token used for polling requests. +#[derive(Clone, Debug, Model)] +pub struct PollingToken { + pub id: I, + pub token: String, + pub device_id: Id, + pub created_at: NaiveDateTime, +} + +impl PollingToken { + #[must_use] + pub fn new(device_id: Id) -> Self { + Self { + id: NoId, + device_id, + token: gen_alphanumeric(32), + created_at: Utc::now().naive_utc(), + } + } +} + +impl PollingToken { + pub async fn find(pool: &PgPool, token: &str) -> Result, SqlxError> { + query_as!( + Self, + "SELECT id, token, device_id, created_at \ + FROM pollingtoken WHERE token = $1", + token + ) + .fetch_optional(pool) + .await + } + + pub async fn delete_for_device_id<'e, E>(executor: E, device_id: Id) -> Result<(), SqlxError> + where + E: PgExecutor<'e>, + { + sqlx::query!("DELETE FROM pollingtoken WHERE device_id = $1", device_id) + .execute(executor) + .await?; + Ok(()) + } +} diff --git a/src/db/models/session.rs b/src/db/models/session.rs index db80fcbb68..4b11068f70 100644 --- a/src/db/models/session.rs +++ b/src/db/models/session.rs @@ -1,9 +1,8 @@ use chrono::{Duration, NaiveDateTime, Utc}; -use sqlx::{query, query_as, Error as SqlxError, PgExecutor, Type}; +use sqlx::{query, query_as, Error as SqlxError, PgExecutor, PgPool, Type}; use webauthn_rs::prelude::{PasskeyAuthentication, PasskeyRegistration}; -use super::DbPool; -use crate::{random::gen_alphanumeric, server_config}; +use crate::{db::Id, random::gen_alphanumeric, server_config}; #[derive(Clone, PartialEq, Type)] #[repr(i16)] @@ -18,7 +17,7 @@ pub enum SessionState { #[derive(Clone)] pub struct Session { pub id: String, - pub user_id: i64, + pub user_id: Id, pub state: SessionState, pub created: NaiveDateTime, pub expires: NaiveDateTime, @@ -31,7 +30,7 @@ pub struct Session { impl Session { #[must_use] pub fn new( - user_id: i64, + user_id: Id, state: SessionState, ip_address: String, device_info: Option, @@ -56,7 +55,7 @@ impl Session { self.expires < Utc::now().naive_utc() } - pub async fn find_by_id(pool: &DbPool, id: &str) -> Result, SqlxError> { + pub async fn find_by_id(pool: &PgPool, id: &str) -> Result, SqlxError> { query_as!( Self, "SELECT id, user_id, state \"state: SessionState\", created, expires, webauthn_challenge, \ @@ -67,7 +66,7 @@ impl Session { .await } - pub async fn save(&self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn save(&self, pool: &PgPool) -> Result<(), SqlxError> { query!( "INSERT INTO session (id, user_id, state, created, expires, webauthn_challenge, web3_challenge, ip_address, device_info) \ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", @@ -83,10 +82,11 @@ impl Session { ) .execute(pool) .await?; + Ok(()) } - pub async fn set_state(&mut self, pool: &DbPool, state: SessionState) -> Result<(), SqlxError> { + pub async fn set_state(&mut self, pool: &PgPool, state: SessionState) -> Result<(), SqlxError> { query!( "UPDATE session SET state = $1 WHERE id = $2", state.clone() as i16, @@ -95,6 +95,7 @@ impl Session { .execute(pool) .await?; self.state = state; + Ok(()) } @@ -130,6 +131,7 @@ impl Session { .await?; self.webauthn_challenge = Some(webauthn_challenge); } + Ok(()) } @@ -151,6 +153,7 @@ impl Session { .await?; self.webauthn_challenge = Some(webauthn_challenge); } + Ok(()) } @@ -170,6 +173,7 @@ impl Session { .execute(executor) .await?; self.web3_challenge = Some(web3_challenge); + Ok(()) } @@ -180,6 +184,7 @@ impl Session { query!("DELETE FROM session WHERE id = $1", self.id) .execute(executor) .await?; + Ok(()) } @@ -190,6 +195,7 @@ impl Session { query!("DELETE FROM session WHERE expires < now()",) .execute(executor) .await?; + Ok(()) } @@ -200,6 +206,7 @@ impl Session { query!("DELETE FROM session WHERE user_id = $1", user_id) .execute(executor) .await?; + Ok(()) } } diff --git a/src/db/models/settings.rs b/src/db/models/settings.rs index ed8257ba57..8d4c6dca55 100644 --- a/src/db/models/settings.rs +++ b/src/db/models/settings.rs @@ -1,10 +1,8 @@ use std::collections::HashMap; -use model_derive::Model; -use sqlx::{query, query_as, Error as SqlxError, PgExecutor, Type}; +use sqlx::{query, query_as, PgExecutor, PgPool, Type}; use struct_patch::Patch; -use super::DbPool; use crate::secret::SecretString; #[derive(Clone, Deserialize, Serialize, PartialEq, Eq, Type, Debug)] @@ -15,11 +13,9 @@ pub enum SmtpEncryption { ImplicitTls, } -#[derive(Debug, Clone, Model, Serialize, Deserialize, PartialEq, Patch)] -#[patch_derive(Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Patch, Serialize)] +#[patch(attribute(derive(Deserialize, Serialize)))] pub struct Settings { - #[serde(skip)] - pub id: Option, // Modules pub openid_enabled: bool, pub wireguard_enabled: bool, @@ -34,10 +30,8 @@ pub struct Settings { // SMTP pub smtp_server: Option, pub smtp_port: Option, - #[model(enum)] pub smtp_encryption: SmtpEncryption, pub smtp_user: Option, - #[model(secret)] pub smtp_password: Option, pub smtp_sender: Option, // Enrollment @@ -52,7 +46,6 @@ pub struct Settings { // LDAP pub ldap_url: Option, pub ldap_bind_username: Option, - #[model(secret)] pub ldap_bind_password: Option, pub ldap_group_search_base: Option, pub ldap_user_search_base: Option, @@ -62,14 +55,135 @@ pub struct Settings { pub ldap_groupname_attr: Option, pub ldap_group_member_attr: Option, pub ldap_member_attr: Option, + // Whether to create a new account when users try to log in with external OpenID + pub openid_create_account: bool, + pub license: Option, } impl Settings { - pub async fn get_settings<'e, E>(executor: E) -> Result + pub async fn get<'e, E>(executor: E) -> Result, sqlx::Error> + where + E: PgExecutor<'e>, + { + query_as!( + Self, + "SELECT openid_enabled, wireguard_enabled, webhooks_enabled, \ + worker_enabled, challenge_template, instance_name, main_logo_url, nav_logo_url, \ + smtp_server, smtp_port, smtp_encryption \"smtp_encryption: _\", smtp_user, \ + smtp_password \"smtp_password?: SecretString\", smtp_sender, \ + enrollment_vpn_step_optional, enrollment_welcome_message, \ + enrollment_welcome_email, enrollment_welcome_email_subject, \ + enrollment_use_welcome_message_as_email, uuid, ldap_url, ldap_bind_username, \ + ldap_bind_password \"ldap_bind_password?: SecretString\", \ + ldap_group_search_base, ldap_user_search_base, ldap_user_obj_class, \ + ldap_group_obj_class, ldap_username_attr, ldap_groupname_attr, \ + ldap_group_member_attr, ldap_member_attr, openid_create_account, \ + license \ + FROM \"settings\" WHERE id = 1", + ) + .fetch_optional(executor) + .await + } + + pub async fn save<'e, E>(&self, executor: E) -> Result<(), sqlx::Error> + where + E: PgExecutor<'e>, + { + query!( + "UPDATE \"settings\" SET \ + openid_enabled = $1, \ + wireguard_enabled = $2, \ + webhooks_enabled = $3, \ + worker_enabled = $4, \ + challenge_template = $5, \ + instance_name = $6, \ + main_logo_url = $7, \ + nav_logo_url = $8, \ + smtp_server = $9, \ + smtp_port = $10, \ + smtp_encryption = $11, \ + smtp_user = $12, \ + smtp_password = $13, \ + smtp_sender = $14, \ + enrollment_vpn_step_optional = $15, \ + enrollment_welcome_message = $16, \ + enrollment_welcome_email = $17, \ + enrollment_welcome_email_subject = $18, \ + enrollment_use_welcome_message_as_email = $19, \ + uuid = $20, \ + ldap_url = $21, \ + ldap_bind_username = $22, \ + ldap_bind_password = $23, \ + ldap_group_search_base = $24, \ + ldap_user_search_base = $25, \ + ldap_user_obj_class = $26, \ + ldap_group_obj_class = $27, \ + ldap_username_attr = $28, \ + ldap_groupname_attr = $29, \ + ldap_group_member_attr = $30, \ + ldap_member_attr = $31, \ + openid_create_account = $32, \ + license = $33 \ + WHERE id = 1", + self.openid_enabled, + self.wireguard_enabled, + self.webhooks_enabled, + self.worker_enabled, + self.challenge_template, + self.instance_name, + self.main_logo_url, + self.nav_logo_url, + self.smtp_server, + self.smtp_port, + &self.smtp_encryption as &SmtpEncryption, + self.smtp_user, + &self.smtp_password as &Option, + self.smtp_sender, + self.enrollment_vpn_step_optional, + self.enrollment_welcome_message, + self.enrollment_welcome_email, + self.enrollment_welcome_email_subject, + self.enrollment_use_welcome_message_as_email, + self.uuid, + self.ldap_url, + self.ldap_bind_username, + &self.ldap_bind_password as &Option, + self.ldap_group_search_base, + self.ldap_user_search_base, + self.ldap_user_obj_class, + self.ldap_group_obj_class, + self.ldap_username_attr, + self.ldap_groupname_attr, + self.ldap_group_member_attr, + self.ldap_member_attr, + self.openid_create_account, + self.license + ) + .execute(executor) + .await?; + + Ok(()) + } + + pub(crate) async fn save_license<'e, E>(&self, executor: E) -> Result<(), sqlx::Error> + where + E: PgExecutor<'e>, + { + query!( + "UPDATE \"settings\" SET license = $1 WHERE id = 1", + self.license, + ) + .execute(executor) + .await?; + + Ok(()) + } + + pub async fn get_settings<'e, E>(executor: E) -> Result where E: PgExecutor<'e>, { - let settings = Settings::find_by_id(executor, 1).await?; + let settings = Settings::get(executor).await?; Ok(settings.expect("Settings not found")) } @@ -77,7 +191,7 @@ impl Settings { // Set default values for settings if not set yet. // This is only relevant to a subset of settings which are nullable // and we want to initialize their values. - pub async fn init_defaults(pool: &DbPool) -> Result<(), SqlxError> { + pub async fn init_defaults(pool: &PgPool) -> Result<(), sqlx::Error> { info!("Initializing default settings"); let default_settings = HashMap::from([ @@ -122,7 +236,7 @@ pub struct SettingsEssentials { } impl SettingsEssentials { - pub(crate) async fn get_settings_essentials<'e, E>(executor: E) -> Result + pub(crate) async fn get_settings_essentials<'e, E>(executor: E) -> Result where E: PgExecutor<'e>, { diff --git a/src/db/models/user.rs b/src/db/models/user.rs index a56aedf224..21fee1ac1f 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -9,19 +9,19 @@ use argon2::{ }; use axum::http::StatusCode; use model_derive::Model; -use otpauth::TOTP; -use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgExecutor, Type}; +use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgExecutor, PgPool, Type}; +use totp_lite::{totp_custom, Sha1}; use super::{ device::{Device, UserDevice}, group::Group, wallet::Wallet, webauthn::WebAuthn, - DbPool, MFAInfo, OAuth2AuthorizedAppInfo, SecurityKey, WalletInfo, + MFAInfo, OAuth2AuthorizedAppInfo, SecurityKey, WalletInfo, }; use crate::{ - auth::TOTP_CODE_VALIDITY_PERIOD, - db::Session, + auth::{EMAIL_CODE_DIGITS, TOTP_CODE_DIGITS, TOTP_CODE_VALIDITY_PERIOD}, + db::{Id, NoId, Session}, error::WebError, random::{gen_alphanumeric, gen_totp_secret}, server_config, @@ -58,7 +58,7 @@ impl fmt::Display for MFAMethod { // User information ready to be sent as part of diagnostic data. #[derive(Serialize)] pub struct UserDiagnostic { - pub id: i64, + pub id: Id, pub mfa_enabled: bool, pub totp_enabled: bool, pub email_mfa_enabled: bool, @@ -67,9 +67,9 @@ pub struct UserDiagnostic { pub enrolled: bool, } -#[derive(Model, PartialEq, Serialize, Clone, Debug)] -pub struct User { - pub id: Option, +#[derive(Clone, Debug, Model, PartialEq, Serialize)] +pub struct User { + pub id: I, pub username: String, pub(crate) password_hash: Option, pub last_name: String, @@ -78,6 +78,9 @@ pub struct User { pub phone: Option, pub mfa_enabled: bool, pub is_active: bool, + /// The user's sub claim returned by the OpenID provider. Also indicates whether the user has + /// used OpenID to log in. + pub openid_sub: Option, // secret has been verified and TOTP can be used pub(crate) totp_enabled: bool, pub(crate) email_mfa_enabled: bool, @@ -89,14 +92,14 @@ pub struct User { pub(crate) recovery_codes: Vec, } -impl User { - fn hash_password(password: &str) -> Result { - let salt = SaltString::generate(&mut OsRng); - Ok(Argon2::default() - .hash_password(password.as_bytes(), &salt)? - .to_string()) - } +fn hash_password(password: &str) -> Result { + let salt = SaltString::generate(&mut OsRng); + Ok(Argon2::default() + .hash_password(password.as_bytes(), &salt)? + .to_string()) +} +impl User { #[must_use] pub fn new>( username: S, @@ -106,10 +109,9 @@ impl User { email: S, phone: Option, ) -> Self { - let password_hash = - password.and_then(|password_hash| Self::hash_password(password_hash).ok()); + let password_hash = password.and_then(|password_hash| hash_password(password_hash).ok()); Self { - id: None, + id: NoId, username: username.into(), password_hash, last_name: last_name.into(), @@ -124,11 +126,14 @@ impl User { mfa_method: MFAMethod::None, recovery_codes: Vec::new(), is_active: true, + openid_sub: None, } } +} +impl User { pub fn set_password(&mut self, password: &str) { - self.password_hash = Self::hash_password(password).ok(); + self.password_hash = hash_password(password).ok(); } pub fn verify_password(&self, password: &str) -> Result<(), HashError> { @@ -154,22 +159,31 @@ impl User { format!("{} {}", self.first_name, self.last_name) } + /// Check if user is enrolled. + /// We assume the user is enrolled if they have a password set + /// or they have logged in using an external OIDC. + #[must_use] + pub fn is_enrolled(&self) -> bool { + self.password_hash.is_some() || self.openid_sub.is_some() + } +} + +impl User { /// Generate new TOTP secret, save it, then return it as RFC 4648 base32-encoded string. pub async fn new_totp_secret<'e, E>(&mut self, executor: E) -> Result where E: PgExecutor<'e>, { let secret = gen_totp_secret(); - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET totp_secret = $1 WHERE id = $2", - secret, - id - ) - .execute(executor) - .await?; - } - let secret_base32 = TOTP::from_bytes(&secret).base32_secret(); + query!( + "UPDATE \"user\" SET totp_secret = $1 WHERE id = $2", + secret, + self.id + ) + .execute(executor) + .await?; + + let secret_base32 = base32::encode(base32::Alphabet::Rfc4648 { padding: false }, &secret); self.totp_secret = Some(secret); Ok(secret_base32) } @@ -180,16 +194,16 @@ impl User { E: PgExecutor<'e>, { let email_secret = gen_totp_secret(); - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET email_mfa_secret = $1 WHERE id = $2", - email_secret, - id - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET email_mfa_secret = $1 WHERE id = $2", + email_secret, + self.id + ) + .execute(executor) + .await?; + self.email_mfa_secret = Some(email_secret); + Ok(()) } @@ -205,15 +219,13 @@ impl User { "Setting MFA method for user {} to {mfa_method:?}", self.username ); - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_method = $2 WHERE id = $1", - id, - &mfa_method as &MFAMethod - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET mfa_method = $2 WHERE id = $1", + self.id, + &mfa_method as &MFAMethod + ) + .execute(executor) + .await?; self.mfa_method = mfa_method; Ok(()) @@ -232,26 +244,22 @@ impl User { return Ok(true); } - if let Some(id) = self.id { - query_scalar!( - "SELECT totp_enabled OR email_mfa_enabled OR coalesce(bool_or(wallet.use_for_mfa), FALSE) \ - OR count(webauthn.id) > 0 \"bool!\" FROM \"user\" \ - LEFT JOIN wallet ON wallet.user_id = \"user\".id \ - LEFT JOIN webauthn ON webauthn.user_id = \"user\".id \ - WHERE \"user\".id = $1 GROUP BY totp_enabled, email_mfa_enabled;", - id - ) - .fetch_one(executor) - .await - } else { - Ok(false) - } + query_scalar!( + "SELECT totp_enabled OR email_mfa_enabled OR coalesce(bool_or(wallet.use_for_mfa), FALSE) \ + OR count(webauthn.id) > 0 \"bool!\" FROM \"user\" \ + LEFT JOIN wallet ON wallet.user_id = \"user\".id \ + LEFT JOIN webauthn ON webauthn.user_id = \"user\".id \ + WHERE \"user\".id = $1 GROUP BY totp_enabled, email_mfa_enabled;", + self.id + ) + .fetch_one(executor) + .await } /// Verify the state of mfa flags are correct. /// Recovers from invalid mfa_method /// Use this function after removing any of the authentication factors. - pub async fn verify_mfa_state(&mut self, pool: &DbPool) -> Result<(), WebError> { + pub async fn verify_mfa_state(&mut self, pool: &PgPool) -> Result<(), WebError> { if let Some(info) = MFAInfo::for_user(pool, self).await? { let factors_present = info.mfa_available(); if self.mfa_enabled != factors_present { @@ -261,15 +269,13 @@ impl User { self.disable_mfa(pool).await?; } else { // first factor was added so MFA needs to be enabled - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_enabled = $2 WHERE id = $1", - id, - factors_present - ) - .execute(pool) - .await?; - } + query!( + "UPDATE \"user\" SET mfa_enabled = $2 WHERE id = $1", + self.id, + factors_present + ) + .execute(pool) + .await?; }; if !factors_present && self.mfa_method != MFAMethod::None { @@ -309,7 +315,7 @@ impl User { } /// Enable MFA. At least one of the authenticator factors must be configured. - pub async fn enable_mfa(&mut self, pool: &DbPool) -> Result<(), WebError> { + pub async fn enable_mfa(&mut self, pool: &PgPool) -> Result<(), WebError> { if !self.mfa_enabled { self.verify_mfa_state(pool).await?; } @@ -333,38 +339,36 @@ impl User { let code = gen_alphanumeric(16); self.recovery_codes.push(code); } - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET recovery_codes = $2 WHERE id = $1", - id, - &self.recovery_codes - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET recovery_codes = $2 WHERE id = $1", + self.id, + &self.recovery_codes + ) + .execute(executor) + .await?; Ok(Some(self.recovery_codes.clone())) } /// Disable MFA; discard recovery codes, TOTP secret, and security keys. - pub async fn disable_mfa(&mut self, pool: &DbPool) -> Result<(), SqlxError> { - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_enabled = FALSE, mfa_method = 'none', totp_enabled = FALSE, email_mfa_enabled = FALSE, \ - totp_secret = NULL, email_mfa_secret = NULL, recovery_codes = '{}' WHERE id = $1", - id - ) - .execute(pool) - .await?; - Wallet::disable_mfa_for_user(pool, id).await?; - WebAuthn::delete_all_for_user(pool, id).await?; - } + pub async fn disable_mfa(&mut self, pool: &PgPool) -> Result<(), SqlxError> { + query!( + "UPDATE \"user\" SET mfa_enabled = FALSE, mfa_method = 'none', totp_enabled = FALSE, email_mfa_enabled = FALSE, \ + totp_secret = NULL, email_mfa_secret = NULL, recovery_codes = '{}' WHERE id = $1", + self.id + ) + .execute(pool) + .await?; + Wallet::disable_mfa_for_user(pool, self.id).await?; + WebAuthn::delete_all_for_user(pool, self.id).await?; + self.totp_secret = None; self.email_mfa_secret = None; self.totp_enabled = false; self.email_mfa_enabled = false; self.mfa_method = MFAMethod::None; self.recovery_codes.clear(); + Ok(()) } @@ -374,36 +378,38 @@ impl User { E: PgExecutor<'e>, { if !self.totp_enabled { - if let Some(id) = self.id { - query!("UPDATE \"user\" SET totp_enabled = TRUE WHERE id = $1", id) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET totp_enabled = TRUE WHERE id = $1", + self.id + ) + .execute(executor) + .await?; self.totp_enabled = true; } + Ok(()) } /// Disable TOTP; discard the secret. - pub async fn disable_totp(&mut self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn disable_totp(&mut self, pool: &PgPool) -> Result<(), SqlxError> { if self.totp_enabled { // FIXME: check if this flag is set correctly when TOTP is the only method self.mfa_enabled = self.check_mfa_enabled(pool).await?; self.totp_enabled = false; self.totp_secret = None; - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_enabled = $2, totp_enabled = $3 AND totp_secret = $4 \ - WHERE id = $1", - id, - self.mfa_enabled, - self.totp_enabled, - self.totp_secret, - ) - .execute(pool) - .await?; - } + + query!( + "UPDATE \"user\" SET mfa_enabled = $2, totp_enabled = $3 AND totp_secret = $4 \ + WHERE id = $1", + self.id, + self.mfa_enabled, + self.totp_enabled, + self.totp_secret, + ) + .execute(pool) + .await?; } + Ok(()) } @@ -413,49 +419,49 @@ impl User { E: PgExecutor<'e>, { if !self.email_mfa_enabled { - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET email_mfa_enabled = TRUE WHERE id = $1", - id - ) - .execute(executor) - .await?; - } + query!( + "UPDATE \"user\" SET email_mfa_enabled = TRUE WHERE id = $1", + self.id + ) + .execute(executor) + .await?; + self.email_mfa_enabled = true; } + Ok(()) } /// Disable email MFA; discard the secret. - pub async fn disable_email_mfa(&mut self, pool: &DbPool) -> Result<(), SqlxError> { + pub async fn disable_email_mfa(&mut self, pool: &PgPool) -> Result<(), SqlxError> { if self.email_mfa_enabled { self.mfa_enabled = self.check_mfa_enabled(pool).await?; self.email_mfa_enabled = false; self.email_mfa_secret = None; - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET mfa_enabled = $2, email_mfa_enabled = $3 AND email_mfa_secret = $4 \ - WHERE id = $1", - id, - self.mfa_enabled, - self.email_mfa_enabled, - self.email_mfa_secret, - ) - .execute(pool) - .await?; - } + + query!( + "UPDATE \"user\" SET mfa_enabled = $2, email_mfa_enabled = $3 AND email_mfa_secret = $4 \ + WHERE id = $1", + self.id, + self.mfa_enabled, + self.email_mfa_enabled, + self.email_mfa_secret, + ) + .execute(pool) + .await?; } + Ok(()) } /// Select all users without sensitive data. // FIXME: Remove it when Model macro will support SecretString pub async fn all_without_sensitive_data( - pool: &DbPool, + pool: &PgPool, ) -> Result, SqlxError> { let users = query!( "SELECT id, mfa_enabled, totp_enabled, email_mfa_enabled, \ - mfa_method as \"mfa_method: MFAMethod\", password_hash, is_active \ + mfa_method \"mfa_method: MFAMethod\", password_hash, is_active, openid_sub \ FROM \"user\"" ) .fetch_all(pool) @@ -469,73 +475,112 @@ impl User { mfa_enabled: u.mfa_enabled, id: u.id, is_active: u.is_active, - enrolled: u.password_hash.is_some(), + enrolled: u.password_hash.is_some() || u.openid_sub.is_some(), }) .collect(); + Ok(res) } /// Return all members of group pub async fn find_by_group_name( - pool: &DbPool, + pool: &PgPool, group_name: &str, - ) -> Result, SqlxError> { + ) -> Result>, SqlxError> { let users = query_as!( - User, - "SELECT \"user\".id \"id?\", username, password_hash, last_name, first_name, email, \ + Self, + "SELECT \"user\".id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, totp_secret, \ email_mfa_enabled, email_mfa_secret, \ - mfa_method \"mfa_method: _\", recovery_codes, is_active \ - FROM \"user\" - INNER JOIN \"group_user\" ON \"user\".id = \"group_user\".user_id - INNER JOIN \"group\" ON \"group_user\".group_id = \"group\".id + mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ + FROM \"user\" \ + INNER JOIN \"group_user\" ON \"user\".id = \"group_user\".user_id \ + INNER JOIN \"group\" ON \"group_user\".group_id = \"group\".id \ WHERE \"group\".name = $1", group_name ) .fetch_all(pool) .await?; + Ok(users) } /// Check if TOTP `code` is valid. #[must_use] - pub fn verify_totp_code(&self, code: u32) -> bool { + pub fn verify_totp_code(&self, code: &str) -> bool { if let Some(totp_secret) = &self.totp_secret { - let totp = TOTP::from_bytes(totp_secret); if let Ok(timestamp) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { - return totp.verify(code, TOTP_CODE_VALIDITY_PERIOD, timestamp.as_secs()); + let expected_code = totp_custom::( + TOTP_CODE_VALIDITY_PERIOD, + TOTP_CODE_DIGITS, + totp_secret, + timestamp.as_secs(), + ); + return code == expected_code; } } + false } - pub fn generate_email_mfa_code(&self) -> Result { - match &self.email_mfa_secret { - Some(email_mfa_secret) => { - let auth = TOTP::from_bytes(email_mfa_secret); - let timeout = &server_config().mfa_code_timeout; - let timestamp = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - let code = auth.generate(timeout.as_secs(), timestamp); + /// Generate MFA code for email verification. + /// + /// NOTE: This code will be valid for two time frames. See comment for verify_email_mfa_code(). + pub fn generate_email_mfa_code(&self) -> Result { + if let Some(email_mfa_secret) = &self.email_mfa_secret { + let timeout = &server_config().mfa_code_timeout; + if let Ok(timestamp) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + let code = totp_custom::( + timeout.as_secs(), + EMAIL_CODE_DIGITS, + email_mfa_secret, + timestamp.as_secs(), + ); Ok(code) + } else { + Err(WebError::EmailMfa("SystemTime before UNIX epoch".into())) } - None => Err(WebError::EmailMfa(format!( + } else { + Err(WebError::EmailMfa(format!( "Email MFA secret not configured for user {}", self.username - ))), + ))) } } /// Check if email MFA `code` is valid. + /// + /// IMPORTANT: because current implementation uses TOTP for email verification, + /// allow the code for the previous time frame. This approach pretends the code is valid + /// for a certain *period of time* (as opposed to a TOTP code which is valid for a certain time *frame*). + /// + /// ```text + /// |<---- frame #0 ---->|<---- frame #1 ---->|<---- frame #2 ---->| + /// |................[*]email sent.................................| + /// |......................[*]email code verified..................| + /// ``` #[must_use] - pub fn verify_email_mfa_code(&self, code: u32) -> bool { + pub fn verify_email_mfa_code(&self, code: &str) -> bool { if let Some(email_mfa_secret) = &self.email_mfa_secret { - let totp = TOTP::from_bytes(email_mfa_secret); - let timeout = &server_config().mfa_code_timeout; + let timeout = server_config().mfa_code_timeout.as_secs(); if let Ok(timestamp) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { - return totp.verify(code, timeout.as_secs(), timestamp.as_secs()); + let expected_code = totp_custom::( + timeout, + EMAIL_CODE_DIGITS, + email_mfa_secret, + timestamp.as_secs(), + ); + if code == expected_code { + return true; + } + + let previous_code = totp_custom::( + timeout, + EMAIL_CODE_DIGITS, + email_mfa_secret, + timestamp.as_secs() - timeout, + ); + return code == previous_code; } } false @@ -544,21 +589,21 @@ impl User { /// Verify recovery code. If it is valid, consume it, so it can't be used again. pub async fn verify_recovery_code( &mut self, - pool: &DbPool, + pool: &PgPool, code: &str, ) -> Result { if let Some(index) = self.recovery_codes.iter().position(|c| c == code) { // Note: swap_remove() should be faster than remove(). self.recovery_codes.swap_remove(index); - if let Some(id) = self.id { - query!( - "UPDATE \"user\" SET recovery_codes = $2 WHERE id = $1", - id, - &self.recovery_codes - ) - .execute(pool) - .await?; - } + + query!( + "UPDATE \"user\" SET recovery_codes = $2 WHERE id = $1", + self.id, + &self.recovery_codes + ) + .execute(pool) + .await?; + Ok(true) } else { Ok(false) @@ -574,9 +619,9 @@ impl User { { query_as!( Self, - "SELECT id \"id?\", username, password_hash, last_name, first_name, email, \ + "SELECT id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, email_mfa_enabled, \ - totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active \ + totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ FROM \"user\" WHERE username = $1", username ) @@ -590,9 +635,9 @@ impl User { { query_as!( Self, - "SELECT id \"id?\", username, password_hash, last_name, first_name, email, \ + "SELECT id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, email_mfa_enabled, \ - totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active \ + totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ FROM \"user\" WHERE email = $1", email ) @@ -600,80 +645,92 @@ impl User { .await } - pub async fn member_of_names<'e, E>(&self, executor: E) -> Result, SqlxError> + pub async fn find_by_sub<'e, E>(executor: E, sub: &str) -> Result, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_scalar!( - "SELECT \"group\".name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id \ - WHERE group_user.user_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + Self, + "SELECT id, username, password_hash, last_name, first_name, email, \ + phone, mfa_enabled, totp_enabled, email_mfa_enabled, \ + totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ + FROM \"user\" WHERE openid_sub = $1", + sub + ) + .fetch_optional(executor) + .await } - pub async fn member_of<'e, E>(&self, executor: E) -> Result, SqlxError> + pub async fn member_of_names<'e, E>(&self, executor: E) -> Result, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - Group, - "SELECT id \"id?\", name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id \ - WHERE group_user.user_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_scalar!( + "SELECT \"group\".name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id \ + WHERE group_user.user_id = $1", + self.id + ) + .fetch_all(executor) + .await } - pub async fn devices(&self, pool: &DbPool) -> Result, SqlxError> { - if let Some(id) = self.id { - let devices = query_as!( - Device, - "SELECT device.id \"id?\", name, wireguard_pubkey, user_id, created \ - FROM device WHERE user_id = $1", - id - ) - .fetch_all(pool) - .await?; + pub async fn member_of<'e, E>(&self, executor: E) -> Result>, SqlxError> + where + E: PgExecutor<'e>, + { + query_as!( + Group, + "SELECT id, name FROM \"group\" JOIN group_user ON \"group\".id = group_user.group_id \ + WHERE group_user.user_id = $1", + self.id + ) + .fetch_all(executor) + .await + } - let mut user_devices = Vec::new(); - for device in devices { - if let Some(user_device) = UserDevice::from_device(pool, device).await? { - user_devices.push(user_device); - } + /// Returns a vector of [`UserDevice`]s (hence the name). + /// [`UserDevice`] is a struct containing additional network info about a device. + /// If you only need [`Device`]s, use [`User::devices()`] instead. + pub async fn user_devices(&self, pool: &PgPool) -> Result, SqlxError> { + let devices = self.devices(pool).await?; + let mut user_devices = Vec::new(); + for device in devices { + if let Some(user_device) = UserDevice::from_device(pool, device).await? { + user_devices.push(user_device); } - Ok(user_devices) - } else { - Ok(Vec::new()) } + + Ok(user_devices) + } + + /// Returns a vector of [`Device`]s related to a user. If you want to get [`UserDevice`]s (which contain additional network info), + /// use [`User::user_devices()`] instead. + pub async fn devices<'e, E>(&self, executor: E) -> Result>, SqlxError> + where + E: PgExecutor<'e>, + { + query_as!( + Device, + "SELECT device.id, name, wireguard_pubkey, user_id, created \ + FROM device WHERE user_id = $1", + self.id + ) + .fetch_all(executor) + .await } pub async fn wallets<'e, E>(&self, executor: E) -> Result, SqlxError> where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - WalletInfo, - "SELECT address \"address!\", name, chain_id, use_for_mfa \ - FROM wallet WHERE user_id = $1 AND validation_timestamp IS NOT NULL", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + WalletInfo, + "SELECT address \"address!\", name, chain_id, use_for_mfa \ + FROM wallet WHERE user_id = $1 AND validation_timestamp IS NOT NULL", + self.id + ) + .fetch_all(executor) + .await } pub async fn oauth2authorizedapps<'e, E>( @@ -683,71 +740,61 @@ impl User { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query_as!( - OAuth2AuthorizedAppInfo, - "SELECT oauth2client.id \"oauth2client_id!\", oauth2client.name \"oauth2client_name\", \ - oauth2authorizedapp.user_id \"user_id\" \ - FROM oauth2authorizedapp \ - JOIN oauth2client ON oauth2client.id = oauth2authorizedapp.oauth2client_id \ - WHERE oauth2authorizedapp.user_id = $1", - id - ) - .fetch_all(executor) - .await - } else { - Ok(Vec::new()) - } + query_as!( + OAuth2AuthorizedAppInfo, + "SELECT oauth2client.id \"oauth2client_id!\", oauth2client.name \"oauth2client_name\", \ + oauth2authorizedapp.user_id \"user_id\" \ + FROM oauth2authorizedapp \ + JOIN oauth2client ON oauth2client.id = oauth2authorizedapp.oauth2client_id \ + WHERE oauth2authorizedapp.user_id = $1", + self.id + ) + .fetch_all(executor) + .await } - pub async fn security_keys(&self, pool: &DbPool) -> Result, SqlxError> { - if let Some(id) = self.id { - query_as!( - SecurityKey, - "SELECT id \"id!\", name FROM webauthn WHERE user_id = $1", - id - ) - .fetch_all(pool) - .await - } else { - Ok(Vec::new()) - } + pub async fn security_keys(&self, pool: &PgPool) -> Result, SqlxError> { + query_as!( + SecurityKey, + "SELECT id \"id!\", name FROM webauthn WHERE user_id = $1", + self.id + ) + .fetch_all(pool) + .await } - pub async fn add_to_group<'e, E>(&self, executor: E, group: &Group) -> Result<(), SqlxError> + pub async fn add_to_group<'e, E>(&self, executor: E, group: &Group) -> Result<(), SqlxError> where E: PgExecutor<'e>, { - if let (Some(id), Some(group_id)) = (self.id, group.id) { - query!( - "INSERT INTO group_user (group_id, user_id) VALUES ($1, $2) \ - ON CONFLICT DO NOTHING", - group_id, - id - ) - .execute(executor) - .await?; - } + query!( + "INSERT INTO group_user (group_id, user_id) VALUES ($1, $2) \ + ON CONFLICT DO NOTHING", + group.id, + self.id + ) + .execute(executor) + .await?; + Ok(()) } pub async fn remove_from_group<'e, E>( &self, executor: E, - group: &Group, + group: &Group, ) -> Result<(), SqlxError> where E: PgExecutor<'e>, { - if let (Some(id), Some(group_id)) = (self.id, group.id) { - query!( - "DELETE FROM group_user WHERE group_id = $1 AND user_id = $2", - group_id, - id - ) - .execute(executor) - .await?; - } + query!( + "DELETE FROM group_user WHERE group_id = $1 AND user_id = $2", + group.id, + self.id + ) + .execute(executor) + .await?; + Ok(()) } @@ -760,25 +807,24 @@ impl User { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - query!( - "DELETE FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = ANY($2)", - id, - app_client_ids - ) - .execute(executor) - .await?; - } + query!( + "DELETE FROM oauth2authorizedapp WHERE user_id = $1 AND oauth2client_id = ANY($2)", + self.id, + app_client_ids + ) + .execute(executor) + .await?; + Ok(()) } /// Create admin user if one doesn't exist yet pub async fn init_admin_user( - pool: &DbPool, + pool: &PgPool, default_admin_pass: &str, ) -> Result<(), anyhow::Error> { info!("Initializing admin user"); - let password_hash = Self::hash_password(default_admin_pass)?; + let password_hash = hash_password(default_admin_pass)?; // create admin user let result = query_scalar!( @@ -807,19 +853,43 @@ impl User { where E: PgExecutor<'e>, { - if let Some(id) = self.id { - Session::delete_all_for_user(executor, id).await?; - } + Session::delete_all_for_user(executor, self.id).await?; + Ok(()) } + + pub async fn find_by_device_id<'e, E>( + executor: E, + device_id: Id, + ) -> Result, SqlxError> + where + E: PgExecutor<'e>, + { + query_as!( + Self, + "SELECT u.id, u.username, u.password_hash, u.last_name, u.first_name, u.email, \ + u.phone, u.mfa_enabled, u.totp_enabled, u.email_mfa_enabled, \ + u.totp_secret, u.email_mfa_secret, u.mfa_method \"mfa_method: _\", u.recovery_codes, u.is_active, u.openid_sub \ + FROM \"user\" u \ + JOIN \"device\" d ON u.id = d.user_id \ + WHERE d.id = $1", + device_id + ) + .fetch_optional(executor) + .await + } } #[cfg(test)] mod test { use super::*; + use crate::{config::DefGuardConfig, SERVER_CONFIG}; #[sqlx::test] - async fn test_user(pool: DbPool) { + async fn test_mfa_code(pool: PgPool) { + let config = DefGuardConfig::new_test_config(); + let _ = SERVER_CONFIG.set(config.clone()); + let mut user = User::new( "hpotter", Some("pass123"), @@ -827,8 +897,33 @@ mod test { "Harry", "h.potter@hogwart.edu.uk", None, + ) + .save(&pool) + .await + .unwrap(); + user.new_email_secret(&pool).await.unwrap(); + assert!(user.email_mfa_secret.is_some()); + let code = user.generate_email_mfa_code().unwrap(); + assert!( + user.verify_email_mfa_code(&code), + "code={code}, secret={:?}", + user.email_mfa_secret.unwrap() ); - user.save(&pool).await.unwrap(); + } + + #[sqlx::test] + async fn test_user(pool: PgPool) { + let mut user = User::new( + "hpotter", + Some("pass123"), + "Potter", + "Harry", + "h.potter@hogwart.edu.uk", + None, + ) + .save(&pool) + .await + .unwrap(); let fetched_user = User::find_by_username(&pool, "hpotter").await.unwrap(); assert!(fetched_user.is_some()); @@ -848,26 +943,30 @@ mod test { } #[sqlx::test] - async fn test_all_users(pool: DbPool) { - let mut harry = User::new( + async fn test_all_users(pool: PgPool) { + User::new( "hpotter", Some("pass123"), "Potter", "Harry", "h.potter@hogwart.edu.uk", None, - ); - harry.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); - let mut albus = User::new( + let albus = User::new( "adumbledore", Some("magic!"), "Dumbledore", "Albus", "a.dumbledore@hogwart.edu.uk", None, - ); - albus.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); let users = User::all(&pool).await.unwrap(); assert_eq!(users.len(), 2); @@ -879,7 +978,7 @@ mod test { } #[sqlx::test] - async fn test_recovery_codes(pool: DbPool) { + async fn test_recovery_codes(pool: PgPool) { let mut harry = User::new( "hpotter", Some("pass123"), @@ -887,10 +986,12 @@ mod test { "Harry", "h.potter@hogwart.edu.uk", None, - ); + ) + .save(&pool) + .await + .unwrap(); harry.get_recovery_codes(&pool).await.unwrap(); assert_eq!(harry.recovery_codes.len(), RECOVERY_CODES_COUNT); - harry.save(&pool).await.unwrap(); let fetched_user = User::find_by_username(&pool, "hpotter").await.unwrap(); assert!(fetched_user.is_some()); diff --git a/src/db/models/wallet.rs b/src/db/models/wallet.rs index e5de96e6f3..557395ccd9 100644 --- a/src/db/models/wallet.rs +++ b/src/db/models/wallet.rs @@ -1,7 +1,4 @@ -use std::{ - error::Error, - fmt::{Display, Formatter, Result as FmtResult}, -}; +use std::{error::Error, fmt}; use chrono::{NaiveDateTime, Utc}; use ethers_core::types::transaction::eip712::{Eip712, TypedData}; @@ -11,11 +8,13 @@ use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, Message, Secp256k1, }; -use sqlx::{query, query_as, Error as SqlxError, PgExecutor}; +use sqlx::{query, query_as, Error as SqlxError, PgExecutor, PgPool}; use tiny_keccak::{Hasher, Keccak}; -use super::DbPool; -use crate::hex::hex_decode; +use crate::{ + db::{Id, NoId}, + hex::hex_decode, +}; #[derive(Debug)] pub enum Web3Error { @@ -30,8 +29,8 @@ pub enum Web3Error { impl Error for Web3Error {} -impl Display for Web3Error { - fn fmt(&self, f: &mut Formatter) -> FmtResult { +impl fmt::Display for Web3Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Decode => write!(f, "hex decoding error"), Self::InvalidMessage => write!(f, "invalid message"), @@ -62,12 +61,12 @@ pub fn hash_message>(message: S) -> [u8; 32] { } #[derive(Model)] -pub struct Wallet { - pub(crate) id: Option, - pub(crate) user_id: i64, +pub struct Wallet { + pub(crate) id: I, + pub(crate) user_id: Id, pub address: String, pub name: String, - pub chain_id: i64, + pub chain_id: Id, pub challenge_message: String, pub challenge_signature: Option, pub creation_timestamp: NaiveDateTime, @@ -78,14 +77,14 @@ pub struct Wallet { impl Wallet { #[must_use] pub fn new_for_user>( - user_id: i64, + user_id: Id, address: S, name: S, - chain_id: i64, + chain_id: Id, challenge_message: S, ) -> Self { Self { - id: None, + id: NoId, user_id, address: address.into(), name: name.into(), @@ -98,6 +97,43 @@ impl Wallet { } } + /// Prepare challenge message using EIP-712 format + #[must_use] + pub fn format_challenge(address: &str, challenge_message: &str) -> String { + let nonce = Nonce::new_random(); + + format!( + r#"{{ + "domain": {{ "name": "Defguard", "version": "1" }}, + "types": {{ + "EIP712Domain": [ + {{ "name": "name", "type": "string" }}, + {{ "name": "version", "type": "string" }} + ], + "ProofOfOwnership": [ + {{ "name": "wallet", "type": "address" }}, + {{ "name": "content", "type": "string" }}, + {{ "name": "nonce", "type": "string" }} + ] + }}, + "primaryType": "ProofOfOwnership", + "message": {{ + "wallet": "{}", + "content": "{}", + "nonce": "{}" + }}}} + "#, + address, + challenge_message, + nonce.secret() + ) + .chars() + .filter(|c| c != &'\r' && c != &'\n' && c != &'\t') + .collect() + } +} + +impl Wallet { pub fn verify_address(&self, message: &str, signature: &str) -> Result { let address_array = hex_decode(&self.address).map_err(|_| Web3Error::Decode)?; let signature_array = hex_decode(signature).map_err(|_| Web3Error::Decode)?; @@ -134,59 +170,27 @@ impl Wallet { Err(Web3Error::VerifyAddress) } } +} - pub async fn set_signature(&mut self, pool: &DbPool, signature: &str) -> Result<(), SqlxError> { +impl Wallet { + pub async fn set_signature(&mut self, pool: &PgPool, signature: &str) -> Result<(), SqlxError> { self.challenge_signature = Some(signature.into()); self.validation_timestamp = Some(Utc::now().naive_utc()); - if let Some(id) = self.id { - query!( - "UPDATE wallet SET challenge_signature = $1, validation_timestamp = $2 WHERE id = $3", - self.challenge_signature, self.validation_timestamp, id - ) - .execute(pool) - .await?; - } - Ok(()) - } - - /// Prepare challenge message using EIP-712 format - #[must_use] - pub fn format_challenge(address: &str, challenge_message: &str) -> String { - let nonce = Nonce::new_random(); - - format!( - r#"{{ - "domain": {{ "name": "Defguard", "version": "1" }}, - "types": {{ - "EIP712Domain": [ - {{ "name": "name", "type": "string" }}, - {{ "name": "version", "type": "string" }} - ], - "ProofOfOwnership": [ - {{ "name": "wallet", "type": "address" }}, - {{ "name": "content", "type": "string" }}, - {{ "name": "nonce", "type": "string" }} - ] - }}, - "primaryType": "ProofOfOwnership", - "message": {{ - "wallet": "{}", - "content": "{}", - "nonce": "{}" - }}}} - "#, - address, - challenge_message, - nonce.secret() + query!( + "UPDATE wallet SET challenge_signature = $1, validation_timestamp = $2 WHERE id = $3", + self.challenge_signature, + self.validation_timestamp, + self.id ) - .chars() - .filter(|c| c != &'\r' && c != &'\n' && c != &'\t') - .collect() + .execute(pool) + .await?; + + Ok(()) } pub async fn find_by_user_and_address<'e, E>( executor: E, - user_id: i64, + user_id: Id, address: &str, ) -> Result, SqlxError> where @@ -194,7 +198,7 @@ impl Wallet { { query_as!( Self, - "SELECT id \"id?\", user_id, address, name, chain_id, challenge_message, challenge_signature, \ + "SELECT id, user_id, address, name, chain_id, challenge_message, challenge_signature, \ creation_timestamp, validation_timestamp, use_for_mfa FROM wallet \ WHERE user_id = $1 AND address = $2", user_id, @@ -204,7 +208,7 @@ impl Wallet { .await } - pub async fn disable_mfa_for_user<'e, E>(executor: E, user_id: i64) -> Result<(), SqlxError> + pub async fn disable_mfa_for_user<'e, E>(executor: E, user_id: Id) -> Result<(), SqlxError> where E: PgExecutor<'e>, { @@ -214,6 +218,7 @@ impl Wallet { ) .execute(executor) .await?; + Ok(()) } } diff --git a/src/db/models/webauthn.rs b/src/db/models/webauthn.rs index 2a7bbfe62f..29fbe4695b 100644 --- a/src/db/models/webauthn.rs +++ b/src/db/models/webauthn.rs @@ -1,38 +1,44 @@ use model_derive::Model; -use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgExecutor}; +use sqlx::{query, query_as, query_scalar, Error as SqlxError, PgExecutor, PgPool}; use webauthn_rs::prelude::Passkey; -use super::{error::ModelError, DbPool}; +use super::error::ModelError; +use crate::db::{Id, NoId}; #[derive(Model)] -pub struct WebAuthn { - id: Option, - pub(crate) user_id: i64, +pub struct WebAuthn { + id: I, + pub(crate) user_id: Id, name: String, // serialize from/to [`Passkey`] pub passkey: Vec, } impl WebAuthn { - pub fn new(user_id: i64, name: String, passkey: &Passkey) -> Result { + pub fn new(user_id: Id, name: String, passkey: &Passkey) -> Result { let passkey = serde_cbor::to_vec(passkey).map_err(|_| ModelError::CannotCreate)?; Ok(Self { - id: None, + id: NoId, user_id, name, passkey, }) } +} +impl WebAuthn { /// Serialize [`Passkey`] from binary data. pub(crate) fn passkey(&self) -> Result { let passkey = serde_cbor::from_slice(&self.passkey).map_err(|_| ModelError::CannotCreate)?; + Ok(passkey) } +} +impl WebAuthn { /// Fetch all [`Passkey`]s for a given user. - pub async fn passkeys_for_user(pool: &DbPool, user_id: i64) -> Result, SqlxError> { + pub async fn passkeys_for_user(pool: &PgPool, user_id: Id) -> Result, SqlxError> { query_scalar!("SELECT passkey FROM webauthn WHERE user_id = $1", user_id) .fetch_all(pool) .await @@ -45,10 +51,10 @@ impl WebAuthn { } /// Fetch all for a given user. - pub async fn all_for_user(pool: &DbPool, user_id: i64) -> Result, SqlxError> { + pub async fn all_for_user(pool: &PgPool, user_id: Id) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", user_id, name, passkey FROM webauthn WHERE user_id = $1", + "SELECT id, user_id, name, passkey FROM webauthn WHERE user_id = $1", user_id ) .fetch_all(pool) @@ -56,7 +62,7 @@ impl WebAuthn { } /// Delete all for a given user. - pub async fn delete_all_for_user<'e, E>(executor: E, user_id: i64) -> Result<(), SqlxError> + pub async fn delete_all_for_user<'e, E>(executor: E, user_id: Id) -> Result<(), SqlxError> where E: PgExecutor<'e>, { diff --git a/src/db/models/webhook.rs b/src/db/models/webhook.rs index 5ffd2e4a63..c9eda286c0 100644 --- a/src/db/models/webhook.rs +++ b/src/db/models/webhook.rs @@ -1,7 +1,8 @@ -use super::UserInfo; -use crate::DbPool; use model_derive::Model; -use sqlx::{query_as, Error as SqlxError, FromRow}; +use sqlx::{query_as, Error as SqlxError, FromRow, PgPool}; + +use super::UserInfo; +use crate::db::{Id, NoId}; /// App events which triggers webhook action #[derive(Debug)] @@ -12,7 +13,7 @@ pub enum AppEvent { HWKeyProvision(HWKeyUserData), } /// User data send on HWKeyProvision AppEvent -#[derive(Serialize, Debug)] +#[derive(Debug, Serialize)] pub struct HWKeyUserData { pub username: String, pub email: String, @@ -45,10 +46,9 @@ impl AppEvent { } } -#[derive(Deserialize, Model, Serialize, FromRow, Debug)] -pub struct WebHook { - #[serde(default)] - pub id: Option, +#[derive(Debug, Deserialize, FromRow, Model, Serialize)] +pub struct WebHook { + pub id: I, pub url: String, pub description: String, pub token: String, @@ -59,9 +59,9 @@ pub struct WebHook { pub on_hwkey_provision: bool, } -impl WebHook { +impl WebHook { /// Fetch all enabled webhooks. - pub async fn all_enabled(pool: &DbPool, trigger: &AppEvent) -> Result, SqlxError> { + pub async fn all_enabled(pool: &PgPool, trigger: &AppEvent) -> Result, SqlxError> { let column_name = trigger.column_name(); let query = format!( "SELECT id, url, description, token, enabled, on_user_created, \ @@ -72,10 +72,10 @@ impl WebHook { } /// Find [`WebHook`] by URL. - pub async fn find_by_url(pool: &DbPool, url: &str) -> Result, SqlxError> { + pub async fn find_by_url(pool: &PgPool, url: &str) -> Result, SqlxError> { query_as!( Self, - "SELECT id \"id?\", url, description, token, enabled, on_user_created, \ + "SELECT id, url, description, token, enabled, on_user_created, \ on_user_deleted, on_user_modified, on_hwkey_provision FROM webhook WHERE url = $1", url ) diff --git a/src/db/models/wireguard.rs b/src/db/models/wireguard.rs index beab1f7eed..5bc14aa9e9 100644 --- a/src/db/models/wireguard.rs +++ b/src/db/models/wireguard.rs @@ -1,6 +1,6 @@ use std::{ collections::HashMap, - fmt::{Debug, Display, Formatter}, + fmt, net::{IpAddr, Ipv4Addr}, str::FromStr, }; @@ -10,17 +10,19 @@ use chrono::{Duration, NaiveDateTime, Utc}; use ipnetwork::{IpNetwork, IpNetworkError, NetworkSize}; use model_derive::Model; use rand_core::OsRng; -use sqlx::{query_as, query_scalar, Error as SqlxError, FromRow, PgConnection, PgExecutor}; +use sqlx::{query_as, query_scalar, Error as SqlxError, FromRow, PgConnection, PgExecutor, PgPool}; use thiserror::Error; +use utoipa::ToSchema; use x25519_dalek::{PublicKey, StaticSecret}; use super::{ device::{Device, DeviceError, DeviceInfo, DeviceNetworkInfo, WireguardNetworkDevice}, error::ModelError, - DbPool, User, UserInfo, + User, UserInfo, }; use crate::{ appstate::AppState, + db::{Id, NoId}, grpc::{gateway::Peer, GatewayState}, wg_config::ImportedDevice, }; @@ -29,9 +31,9 @@ pub const DEFAULT_KEEPALIVE_INTERVAL: i32 = 25; pub const DEFAULT_DISCONNECT_THRESHOLD: i32 = 180; // Used in process of importing network from wireguard config -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct MappedDevice { - pub user_id: i64, + pub user_id: Id, pub name: String, pub wireguard_pubkey: String, pub wireguard_ip: IpAddr, @@ -58,19 +60,19 @@ impl DateTimeAggregation { #[derive(Clone, Debug)] pub enum GatewayEvent { - NetworkCreated(i64, WireguardNetwork), - NetworkModified(i64, WireguardNetwork, Vec), - NetworkDeleted(i64, String), + NetworkCreated(Id, WireguardNetwork), + NetworkModified(Id, WireguardNetwork, Vec), + NetworkDeleted(Id, String), DeviceCreated(DeviceInfo), DeviceModified(DeviceInfo), DeviceDeleted(DeviceInfo), } /// Stores configuration required to setup a WireGuard network -#[derive(Clone, Debug, Model, Deserialize, Serialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Model, PartialEq, Serialize, ToSchema)] #[table(wireguard_network)] -pub struct WireguardNetwork { - pub id: Option, +pub struct WireguardNetwork { + pub id: I, pub name: String, #[model(enum)] pub address: IpNetwork, @@ -93,12 +95,15 @@ pub struct WireguardKey { pub public: String, } -impl Display for WireguardNetwork { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.id { - Some(network_id) => write!(f, "[ID {}] {}", network_id, self.name), - None => write!(f, "{}", self.name), - } +impl fmt::Display for WireguardNetwork { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +impl fmt::Display for WireguardNetwork { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "[ID {}] {}", self.id, self.name) } } @@ -137,7 +142,7 @@ impl WireguardNetwork { let prvkey = StaticSecret::random_from_rng(OsRng); let pubkey = PublicKey::from(&prvkey); Ok(Self { - id: None, + id: NoId, name, address, port, @@ -153,11 +158,15 @@ impl WireguardNetwork { }) } - pub fn get_id(&self) -> Result { - let id = self.id.ok_or(ModelError::IdNotSet)?; - Ok(id) + /// Try to set `address` from `&str`. + pub fn try_set_address(&mut self, address: &str) -> Result { + IpNetwork::from_str(address).inspect(|&network| { + self.address = network; + }) } +} +impl WireguardNetwork { pub async fn find_by_name<'e, E>( executor: E, name: &str, @@ -168,7 +177,7 @@ impl WireguardNetwork { let networks = query_as!( WireguardNetwork, "SELECT \ - id as \"id?\", name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ + id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold \ FROM wireguard_network WHERE name = $1", name @@ -204,6 +213,7 @@ impl WireguardNetwork { let count = query_scalar!("SELECT count(*) \"count!\" FROM wireguard_network_device WHERE wireguard_network_id = $1", self.id) .fetch_one(transaction) .await?; + Ok(count) } @@ -222,7 +232,8 @@ impl WireguardNetwork { return Err(WireguardNetworkError::NetworkTooSmall); } } - }; + } + Ok(()) } @@ -237,14 +248,6 @@ impl WireguardNetwork { } } - /// Try to set `address` from `&str`. - pub fn try_set_address(&mut self, address: &str) -> Result { - IpNetwork::from_str(address).map(|network| { - self.address = network; - network - }) - } - /// Try to change network address, changing device addresses if necessary. pub async fn change_address( &mut self, @@ -255,7 +258,6 @@ impl WireguardNetwork { "Changing network address for {self} from {} to {new_address}", self.address ); - let network_id = self.get_id()?; let old_address = self.address; // check if new network size will fit all existing devices @@ -292,11 +294,8 @@ impl WireguardNetwork { } match devices_iter.next() { Some(device) => { - let Some(device_id) = device.id else { - return Err(WireguardNetworkError::from(ModelError::CannotModify)); - }; let wireguard_network_device = - WireguardNetworkDevice::new(network_id, device_id, ip); + WireguardNetworkDevice::new(self.id, device.id, ip); wireguard_network_device.update(&mut *transaction).await?; } None => break, @@ -305,6 +304,7 @@ impl WireguardNetwork { } self.address = new_address; + Ok(()) } @@ -313,38 +313,38 @@ impl WireguardNetwork { async fn get_allowed_devices( &self, transaction: &mut PgConnection, - ) -> Result, ModelError> { + ) -> Result>, ModelError> { debug!("Fetching all allowed devices for network {}", self); - let devices = match self - .get_allowed_groups(&mut *transaction) - .await? { + let devices = match self.get_allowed_groups(&mut *transaction).await? { // devices need to be filtered by allowed group Some(allowed_groups) => { query_as!( - Device, - "SELECT DISTINCT ON (d.id) d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ - FROM device d \ - JOIN \"user\" u ON d.user_id = u.id \ - JOIN group_user gu ON u.id = gu.user_id \ - JOIN \"group\" g ON gu.group_id = g.id \ - WHERE g.\"name\" IN (SELECT * FROM UNNEST($1::text[])) - AND u.is_active = true - ORDER BY d.id ASC", - &allowed_groups - ) + Device, + "SELECT DISTINCT ON (d.id) d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ + FROM device d \ + JOIN \"user\" u ON d.user_id = u.id \ + JOIN group_user gu ON u.id = gu.user_id \ + JOIN \"group\" g ON gu.group_id = g.id \ + WHERE g.\"name\" IN (SELECT * FROM UNNEST($1::text[])) \ + AND u.is_active = true \ + ORDER BY d.id ASC", + &allowed_groups + ) .fetch_all(&mut *transaction) .await? - }, + } // all devices of enabled users are allowed None => { query_as!( Device, - "SELECT d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ + "SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ FROM device d \ JOIN \"user\" u ON d.user_id = u.id \ WHERE u.is_active = true \ ORDER BY d.id ASC" - ).fetch_all(&mut *transaction).await? + ) + .fetch_all(&mut *transaction) + .await? } }; @@ -374,20 +374,19 @@ impl WireguardNetwork { pub async fn add_device_to_network( &self, transaction: &mut PgConnection, - device: &Device, + device: &Device, reserved_ips: Option<&[IpAddr]>, ) -> Result { info!("Assigning IP in network {self} for {device}"); let allowed_devices = self.get_allowed_devices(&mut *transaction).await?; - let allowed_device_ids: Vec = - allowed_devices.iter().filter_map(|dev| dev.id).collect(); - if allowed_device_ids.contains(&device.get_id()?) { + let allowed_device_ids: Vec = allowed_devices.iter().map(|dev| dev.id).collect(); + if allowed_device_ids.contains(&device.id) { let wireguard_network_device = device .assign_network_ip(&mut *transaction, self, reserved_ips) .await?; Ok(wireguard_network_device) } else { - error!("Device {device} not allowed in network {self}"); + info!("Device {device} not allowed in network {self}"); Err(WireguardNetworkError::DeviceNotAllowed(format!("{device}"))) } } @@ -404,9 +403,9 @@ impl WireguardNetwork { // list all allowed devices let allowed_devices = self.get_allowed_devices(&mut *transaction).await?; // convert to a map for easier processing - let mut allowed_devices: HashMap = allowed_devices + let mut allowed_devices: HashMap> = allowed_devices .into_iter() - .filter_map(|dev| dev.id.map(|id| (id, dev))) + .map(|dev| (dev.id, dev)) .collect(); // check if all devices can fit within network @@ -416,11 +415,10 @@ impl WireguardNetwork { // list all assigned IPs let assigned_ips = - WireguardNetworkDevice::all_for_network(&mut *transaction, self.get_id()?).await?; + WireguardNetworkDevice::all_for_network(&mut *transaction, self.id).await?; // loop through assigned IPs; remove no longer allowed, readdress when necessary; remove processed entry from all devices list // initial list should now contain only devices to be added - let network_id = self.get_id()?; let mut events = Vec::new(); for device_network_config in assigned_ips { // device is allowed and an IP was already assigned @@ -433,7 +431,7 @@ impl WireguardNetwork { events.push(GatewayEvent::DeviceModified(DeviceInfo { device, network_info: vec![DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -453,7 +451,7 @@ impl WireguardNetwork { events.push(GatewayEvent::DeviceDeleted(DeviceInfo { device, network_info: vec![DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: device_network_config.wireguard_ip, preshared_key: device_network_config.preshared_key, is_authorized: device_network_config.is_authorized, @@ -475,7 +473,7 @@ impl WireguardNetwork { events.push(GatewayEvent::DeviceCreated(DeviceInfo { device, network_info: vec![DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -495,12 +493,11 @@ impl WireguardNetwork { transaction: &mut PgConnection, imported_devices: Vec, ) -> Result<(Vec, Vec), WireguardNetworkError> { - let network_id = self.get_id()?; let allowed_devices = self.get_allowed_devices(&mut *transaction).await?; // convert to a map for easier processing - let allowed_devices: HashMap = allowed_devices + let allowed_devices: HashMap> = allowed_devices .into_iter() - .filter_map(|dev| dev.id.map(|id| (id, dev))) + .map(|dev| (dev.id, dev)) .collect(); let mut devices_to_map = Vec::new(); @@ -513,16 +510,15 @@ impl WireguardNetwork { { Some(existing_device) => { // check if device is allowed in network - let device_id = existing_device.get_id()?; - match allowed_devices.get(&device_id) { + match allowed_devices.get(&existing_device.id) { Some(_) => { info!( "Device with pubkey {} exists already, assigning IP {} for new network: {self}", existing_device.wireguard_pubkey, imported_device.wireguard_ip ); let wireguard_network_device = WireguardNetworkDevice::new( - network_id, - existing_device.id.expect("Device ID is missing"), + self.id, + existing_device.id, imported_device.wireguard_ip, ); wireguard_network_device.insert(&mut *transaction).await?; @@ -532,7 +528,7 @@ impl WireguardNetwork { events.push(GatewayEvent::DeviceModified(DeviceInfo { device: existing_device, network_info: vec![DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -550,6 +546,7 @@ impl WireguardNetwork { None => devices_to_map.push(imported_device), } } + Ok((devices_to_map, events)) } @@ -560,7 +557,6 @@ impl WireguardNetwork { mapped_devices: Vec, ) -> Result, WireguardNetworkError> { info!("Mapping user devices for network {}", self); - let network_id = self.get_id()?; // get allowed groups for network let allowed_groups = self.get_allowed_groups(&mut *transaction).await?; @@ -574,12 +570,13 @@ impl WireguardNetwork { WireguardNetworkError::InvalidDevicePubkey(mapped_device.wireguard_pubkey.clone()) })?; // save a new device - let mut device = Device::new( + let device = Device::new( mapped_device.name.clone(), mapped_device.wireguard_pubkey.clone(), mapped_device.user_id, - ); - device.save(&mut *transaction).await?; + ) + .save(&mut *transaction) + .await?; debug!("Saved new device {device}"); // get a list of groups user is assigned to @@ -601,14 +598,11 @@ impl WireguardNetwork { let mut network_info = Vec::new(); match &allowed_groups { None => { - let wireguard_network_device = WireguardNetworkDevice::new( - network_id, - device.id.expect("Device ID is missing"), - mapped_device.wireguard_ip, - ); + let wireguard_network_device = + WireguardNetworkDevice::new(self.id, device.id, mapped_device.wireguard_ip); wireguard_network_device.insert(&mut *transaction).await?; network_info.push(DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -619,13 +613,13 @@ impl WireguardNetwork { if allowed.iter().any(|group| groups.contains(group)) { // assign specified IP in imported network let wireguard_network_device = WireguardNetworkDevice::new( - network_id, - device.id.expect("Device ID is missing"), + self.id, + device.id, mapped_device.wireguard_ip, ); wireguard_network_device.insert(&mut *transaction).await?; network_info.push(DeviceNetworkInfo { - network_id, + network_id: self.id, device_wireguard_ip: wireguard_network_device.wireguard_ip, preshared_key: wireguard_network_device.preshared_key, is_authorized: wireguard_network_device.is_authorized, @@ -648,17 +642,18 @@ impl WireguardNetwork { })); } } + Ok(events) } async fn fetch_latest_stats( &self, - conn: &DbPool, - device_id: i64, - ) -> Result, SqlxError> { + conn: &PgPool, + device_id: Id, + ) -> Result>, SqlxError> { let stats = query_as!( WireguardPeerStats, - "SELECT id \"id?\", device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", \ + "SELECT id, device_id \"device_id!\", collected_at \"collected_at!\", network \"network!\", \ endpoint, upload \"upload!\", download \"download!\", latest_handshake \"latest_handshake!\", allowed_ips \ FROM wireguard_peer_stats \ WHERE device_id = $1 AND network = $2 \ @@ -669,11 +664,12 @@ impl WireguardNetwork { ) .fetch_optional(conn) .await?; + Ok(stats) } /// Parse WireGuard IP address - fn parse_wireguard_ip(stats: &WireguardPeerStats) -> Option { + fn parse_wireguard_ip(stats: &WireguardPeerStats) -> Option { stats .allowed_ips .as_ref() @@ -681,7 +677,7 @@ impl WireguardNetwork { } /// Parse public IP address - fn parse_public_ip(stats: &WireguardPeerStats) -> Option { + fn parse_public_ip(stats: &WireguardPeerStats) -> Option { stats .endpoint .as_ref() @@ -691,8 +687,8 @@ impl WireguardNetwork { /// Finds when the device connected based on handshake timestamps async fn connected_at( &self, - conn: &DbPool, - device_id: i64, + conn: &PgPool, + device_id: Id, ) -> Result, SqlxError> { let connected_at = query_scalar!( "SELECT \ @@ -710,14 +706,15 @@ impl WireguardNetwork { ) .fetch_optional(conn) .await?; + Ok(connected_at.flatten()) } /// Retrieves stats for specified devices async fn device_stats( &self, - conn: &DbPool, - devices: &[Device], + conn: &PgPool, + devices: &[Device], from: &NaiveDateTime, aggregation: &DateTimeAggregation, ) -> Result, SqlxError> { @@ -730,15 +727,15 @@ impl WireguardNetwork { // https://github.com/launchbadge/sqlx/issues/656 let device_ids = devices .iter() - .filter_map(|d| d.id.map(|id| id.to_string())) + .map(|d| d.id.to_string()) .collect::>() .join(","); let query = format!( "SELECT \ device_id, \ - date_trunc($1, collected_at) as collected_at, \ - cast(sum(download) as bigint) as download, \ - cast(sum(upload) as bigint) as upload \ + date_trunc($1, collected_at) collected_at, \ + cast(sum(download) as bigint) download, \ + cast(sum(upload) as bigint) upload \ FROM wireguard_peer_stats_view \ WHERE device_id IN ({device_ids}) \ AND collected_at >= $2 \ @@ -754,19 +751,18 @@ impl WireguardNetwork { .await?; let mut result = Vec::new(); for device in devices { - let Some(device_id) = device.id else { continue }; - let latest_stats = self.fetch_latest_stats(conn, device_id).await?; + let latest_stats = self.fetch_latest_stats(conn, device.id).await?; result.push(WireguardDeviceStatsRow { - id: device_id, + id: device.id, user_id: device.user_id, name: device.name.clone(), wireguard_ip: latest_stats.as_ref().and_then(Self::parse_wireguard_ip), public_ip: latest_stats.as_ref().and_then(Self::parse_public_ip), - connected_at: self.connected_at(conn, device_id).await?, + connected_at: self.connected_at(conn, device.id).await?, // Filter stats for this device stats: stats .iter() - .filter(|s| Some(s.device_id) == device.id) + .filter(|s| s.device_id == device.id) .cloned() .collect(), }); @@ -777,11 +773,11 @@ impl WireguardNetwork { /// Retrieves network stats grouped by currently active users since `from` timestamp pub async fn user_stats( &self, - conn: &DbPool, + conn: &PgPool, from: &NaiveDateTime, aggregation: &DateTimeAggregation, ) -> Result, SqlxError> { - let mut user_map: HashMap> = HashMap::new(); + let mut user_map: HashMap> = HashMap::new(); let oldest_handshake = (Utc::now() - Duration::minutes(WIREGUARD_MAX_HANDSHAKE_MINUTES)).naive_utc(); // Retrieve connected devices from database @@ -793,7 +789,7 @@ impl WireguardNetwork { ORDER BY device_id, latest_handshake DESC \ ) \ SELECT \ - d.id \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ + d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ FROM device d \ JOIN s ON d.id = s.device_id \ WHERE s.latest_handshake >= $1 AND s.network = $2", @@ -818,20 +814,21 @@ impl WireguardNetwork { devices: u.1.clone(), }); } + Ok(stats) } /// Retrieves total active users/devices since `from` timestamp async fn total_activity( &self, - conn: &DbPool, + conn: &PgPool, from: &NaiveDateTime, ) -> Result { let activity_stats = query_as!( WireguardNetworkActivityStats, "SELECT \ - COALESCE(COUNT(DISTINCT(u.id)), 0) as \"active_users!\", \ - COALESCE(COUNT(DISTINCT(s.device_id)), 0) as \"active_devices!\" \ + COALESCE(COUNT(DISTINCT(u.id)), 0) \"active_users!\", \ + COALESCE(COUNT(DISTINCT(s.device_id)), 0) \"active_devices!\" \ FROM \"user\" u \ JOIN device d ON d.user_id = u.id \ JOIN wireguard_peer_stats s ON s.device_id = d.id \ @@ -841,20 +838,21 @@ impl WireguardNetwork { ) .fetch_one(conn) .await?; + Ok(activity_stats) } /// Retrieves currently connected users async fn current_activity( &self, - conn: &DbPool, + conn: &PgPool, ) -> Result { let from = (Utc::now() - Duration::minutes(WIREGUARD_MAX_HANDSHAKE_MINUTES)).naive_utc(); let activity_stats = query_as!( WireguardNetworkActivityStats, "SELECT \ - COALESCE(COUNT(DISTINCT(u.id)), 0) as \"active_users!\", \ - COALESCE(COUNT(DISTINCT(s.device_id)), 0) as \"active_devices!\" \ + COALESCE(COUNT(DISTINCT(u.id)), 0) \"active_users!\", \ + COALESCE(COUNT(DISTINCT(s.device_id)), 0) \"active_devices!\" \ FROM \"user\" u \ JOIN device d ON d.user_id = u.id \ JOIN wireguard_peer_stats s ON s.device_id = d.id \ @@ -864,6 +862,7 @@ impl WireguardNetwork { ) .fetch_one(conn) .await?; + Ok(activity_stats) } @@ -871,7 +870,7 @@ impl WireguardNetwork { /// using `aggregation` (hour/minute) aggregation level async fn transfer_series( &self, - conn: &DbPool, + conn: &PgPool, from: &NaiveDateTime, aggregation: &DateTimeAggregation, ) -> Result, SqlxError> { @@ -892,13 +891,14 @@ impl WireguardNetwork { ) .fetch_all(conn) .await?; + Ok(stats) } /// Retrieves network stats pub async fn network_stats( &self, - conn: &DbPool, + conn: &PgPool, from: &NaiveDateTime, aggregation: &DateTimeAggregation, ) -> Result { @@ -921,7 +921,7 @@ impl WireguardNetwork { impl Default for WireguardNetwork { fn default() -> Self { Self { - id: Option::default(), + id: NoId, name: String::default(), address: IpNetwork::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0).unwrap(), port: i32::default(), @@ -941,7 +941,7 @@ impl Default for WireguardNetwork { #[derive(Serialize, Clone, Debug)] pub struct WireguardNetworkInfo { #[serde(flatten)] - pub network: WireguardNetwork, + pub network: WireguardNetwork, pub connected: bool, pub gateways: Vec, pub allowed_groups: Vec, @@ -956,34 +956,34 @@ pub struct WireguardStatsRow { #[derive(FromRow, Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct WireguardDeviceTransferRow { - pub device_id: i64, + pub device_id: Id, pub collected_at: Option, pub upload: i64, pub download: i64, } -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Clone, Default, Deserialize, Serialize)] pub struct WireguardDeviceStatsRow { - pub id: i64, + pub id: Id, pub stats: Vec, - pub user_id: i64, + pub user_id: Id, pub name: String, pub wireguard_ip: Option, pub public_ip: Option, pub connected_at: Option, } -#[derive(Serialize, Deserialize)] +#[derive(Deserialize, Serialize)] pub struct WireguardUserStatsRow { pub user: UserInfo, pub devices: Vec, } -#[derive(Model, Serialize, Deserialize, Debug)] +#[derive(Debug, Deserialize, Model, Serialize)] #[table(wireguard_peer_stats)] -pub struct WireguardPeerStats { - pub id: Option, - pub device_id: i64, +pub struct WireguardPeerStats { + pub id: I, + pub device_id: Id, pub collected_at: NaiveDateTime, pub network: i64, pub endpoint: Option, @@ -1004,7 +1004,7 @@ pub struct WireguardNetworkTransferStats { pub download: i64, } -#[derive(Serialize, Deserialize)] +#[derive(Deserialize, Serialize)] pub struct WireguardNetworkStats { pub current_active_users: i64, pub current_active_devices: i64, @@ -1019,38 +1019,33 @@ pub struct WireguardNetworkStats { mod test { use chrono::{Duration, SubsecRound}; - use crate::db::models::device::WireguardNetworkDevice; - use super::*; + use crate::db::models::device::WireguardNetworkDevice; - async fn add_devices(pool: &DbPool, network: &WireguardNetwork, count: usize) { - let mut user = User::new( + async fn add_devices(pool: &PgPool, network: &WireguardNetwork, count: usize) { + let user = User::new( "testuser", Some("hunter2"), "Tester", "Test", "test@test.com", None, - ); - user.save(pool).await.unwrap(); + ) + .save(pool) + .await + .unwrap(); for i in 0..count { - Device::new_with_ip( - pool, - user.id.unwrap(), - format!("dev{i}"), - format!("key{i}"), - network, - ) - .await - .unwrap(); + Device::new_with_ip(pool, user.id, format!("dev{i}"), format!("key{i}"), network) + .await + .unwrap(); } } #[sqlx::test] - async fn test_change_address(pool: DbPool) { + async fn test_change_address(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/24").unwrap(); - network.save(&pool).await.unwrap(); + let mut network = network.save(&pool).await.unwrap(); add_devices(&pool, &network, 3).await; @@ -1069,7 +1064,7 @@ mod test { .unwrap() .unwrap(); let wireguard_network_device = - WireguardNetworkDevice::find(&pool, device.id.unwrap(), network.id.unwrap()) + WireguardNetworkDevice::find(&pool, device.id, network.id) .await .unwrap() .unwrap(); @@ -1081,10 +1076,10 @@ mod test { } #[sqlx::test] - async fn test_change_address_wont_fit(pool: DbPool) { + async fn test_change_address_wont_fit(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/29").unwrap(); - network.save(&pool).await.unwrap(); + let mut network = network.save(&pool).await.unwrap(); add_devices(&pool, &network, 3).await; @@ -1100,22 +1095,26 @@ mod test { } #[sqlx::test] - async fn test_connected_at_reconnection(pool: DbPool) { + async fn test_connected_at_reconnection(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/29").unwrap(); - network.save(&pool).await.unwrap(); + let network = network.save(&pool).await.unwrap(); - let mut user = User::new( + let user = User::new( "testuser", Some("hunter2"), "Tester", "Test", "test@test.com", None, - ); - user.save(&pool).await.unwrap(); - let mut device = Device::new(String::new(), String::new(), user.id.unwrap()); - device.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); + let device = Device::new(String::new(), String::new(), user.id) + .save(&pool) + .await + .unwrap(); // insert stats let samples = 60; // 1 hour of samples @@ -1123,22 +1122,24 @@ mod test { for i in 0..=samples { // simulate connection 30 minutes ago let handshake_minutes = i * if i < 31 { 1 } else { 10 }; - let mut wps = WireguardPeerStats { - id: None, - device_id: device.id.unwrap(), + WireguardPeerStats { + id: NoId, + device_id: device.id, collected_at: now - Duration::minutes(i), - network: network.id.unwrap(), + network: network.id, endpoint: Some("11.22.33.44".into()), upload: (samples - i) * 10, download: (samples - i) * 20, latest_handshake: now - Duration::minutes(handshake_minutes), allowed_ips: Some("10.1.1.0/24".into()), - }; - wps.save(&pool).await.unwrap(); + } + .save(&pool) + .await + .unwrap(); } let connected_at = network - .connected_at(&pool, device.id.unwrap()) + .connected_at(&pool, device.id) .await .unwrap() .unwrap(); @@ -1150,43 +1151,49 @@ mod test { } #[sqlx::test] - async fn test_connected_at_always_connected(pool: DbPool) { + async fn test_connected_at_always_connected(pool: PgPool) { let mut network = WireguardNetwork::default(); network.try_set_address("10.1.1.1/29").unwrap(); - network.save(&pool).await.unwrap(); + let network = network.save(&pool).await.unwrap(); - let mut user = User::new( + let user = User::new( "testuser", Some("hunter2"), "Tester", "Test", "test@test.com", None, - ); - user.save(&pool).await.unwrap(); - let mut device = Device::new(String::new(), String::new(), user.id.unwrap()); - device.save(&pool).await.unwrap(); + ) + .save(&pool) + .await + .unwrap(); + let device = Device::new(String::new(), String::new(), user.id) + .save(&pool) + .await + .unwrap(); // insert stats let samples = 60; // 1 hour of samples let now = Utc::now().naive_utc(); for i in 0..=samples { - let mut wps = WireguardPeerStats { - id: None, - device_id: device.id.unwrap(), + WireguardPeerStats { + id: NoId, + device_id: device.id, collected_at: now - Duration::minutes(i), - network: network.id.unwrap(), + network: network.id, endpoint: Some("11.22.33.44".into()), upload: (samples - i) * 10, download: (samples - i) * 20, latest_handshake: now - Duration::minutes(i), // handshake every minute allowed_ips: Some("10.1.1.0/24".into()), - }; - wps.save(&pool).await.unwrap(); + } + .save(&pool) + .await + .unwrap(); } let connected_at = network - .connected_at(&pool, device.id.unwrap()) + .connected_at(&pool, device.id) .await .unwrap() .unwrap(); diff --git a/src/db/models/yubikey.rs b/src/db/models/yubikey.rs index b5f2a3a9cb..ca3d90382f 100644 --- a/src/db/models/yubikey.rs +++ b/src/db/models/yubikey.rs @@ -1,39 +1,43 @@ use model_derive::Model; -use sqlx::{query, query_as, Error as SqlxError, PgExecutor}; +use sqlx::{query, query_as, PgExecutor}; + +use crate::db::{Id, NoId}; #[derive(Deserialize, Model, Serialize)] -pub struct YubiKey { - pub id: Option, +pub struct YubiKey { + pub id: I, pub name: String, pub serial: String, - pub user_id: i64, + pub user_id: Id, } impl YubiKey { #[must_use] - pub fn new(name: String, serial: String, user_id: i64) -> Self { + pub fn new(name: String, serial: String, user_id: Id) -> Self { Self { - id: None, + id: NoId, name, serial, user_id, } } +} - pub async fn find_by_user_id<'e, E>(executor: E, user_id: i64) -> Result, SqlxError> +impl YubiKey { + pub async fn find_by_user_id<'e, E>(executor: E, user_id: Id) -> Result, sqlx::Error> where E: PgExecutor<'e>, { query_as!( Self, - "SELECT * FROM \"yubikey\" WHERE user_id = $1", + "SELECT id, name, serial, user_id FROM \"yubikey\" WHERE user_id = $1", user_id ) .fetch_all(executor) .await } - pub async fn delete_by_id<'e, E>(executor: E, id: i64) -> Result<(), SqlxError> + pub async fn delete_by_id<'e, E>(executor: E, id: Id) -> Result<(), sqlx::Error> where E: PgExecutor<'e>, { diff --git a/src/enterprise/LICENSE.md b/src/enterprise/LICENSE.md new file mode 100644 index 0000000000..059d8803db --- /dev/null +++ b/src/enterprise/LICENSE.md @@ -0,0 +1,16 @@ +Copyright 2024 teonite ventures sp. z o. o. + +defguard enterprise license / defguard.net + +Use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Use is permitted for the purposes of the Licensee that paid for the relevant license only (no redistributions or products based on that). + +2. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote the Licensee when using the product without specific prior written permission. + +3. The Licensee may use the software under the terms and conditions of this license after purchasing a license fee from the Licensor in accordance with the currently available price list defining the time scope of this license on the defguard.net website. Licensor may secure the ability to use the software with a license key or other technical protection. + +4. You may not move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key. + +5. The licensor can provide support for the use of the software. The current terms in this respect are on the website defguard.net +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/enterprise/db/mod.rs b/src/enterprise/db/mod.rs new file mode 100644 index 0000000000..c446ac8833 --- /dev/null +++ b/src/enterprise/db/mod.rs @@ -0,0 +1 @@ +pub mod models; diff --git a/src/enterprise/db/models/enterprise_settings.rs b/src/enterprise/db/models/enterprise_settings.rs new file mode 100644 index 0000000000..1d14b761c0 --- /dev/null +++ b/src/enterprise/db/models/enterprise_settings.rs @@ -0,0 +1,75 @@ +use sqlx::{query, query_as, PgExecutor}; +use struct_patch::Patch; + +use crate::enterprise::license::{get_cached_license, validate_license}; + +#[derive(Debug, Deserialize, Patch, Serialize)] +#[patch(attribute(derive(Deserialize, Serialize)))] +pub struct EnterpriseSettings { + // If true, only admins can manage devices + pub admin_device_management: bool, + // If true, the option to route all traffic through the vpn is disabled in the client + pub disable_all_traffic: bool, + // If true, manual WireGuard setup is disabled + pub only_client_activation: bool, +} + +// We want to be conscious of what the defaults are here +impl Default for EnterpriseSettings { + fn default() -> Self { + Self { + admin_device_management: false, + disable_all_traffic: false, + only_client_activation: false, + } + } +} + +impl EnterpriseSettings { + /// If license is valid returns current [`EnterpriseSettings`] object. + /// Otherwise returns [`EnterpriseSettings::default()`]. + pub async fn get<'e, E>(executor: E) -> Result + where + E: PgExecutor<'e>, + { + // avoid holding the rwlock across await, makes the future !Send + // and therefore unusable in axum handlers + let is_valid = { + let license = get_cached_license(); + validate_license(license.as_ref()).is_ok() + }; + if is_valid { + let settings = query_as!( + Self, + "SELECT admin_device_management, \ + disable_all_traffic, only_client_activation \ + FROM \"enterprisesettings\" WHERE id = 1", + ) + .fetch_optional(executor) + .await?; + Ok(settings.expect("EnterpriseSettings not found")) + } else { + Ok(EnterpriseSettings::default()) + } + } + + pub(crate) async fn save<'e, E>(&self, executor: E) -> Result<(), sqlx::Error> + where + E: PgExecutor<'e>, + { + query!( + "UPDATE \"enterprisesettings\" SET \ + admin_device_management = $1, \ + disable_all_traffic = $2, \ + only_client_activation = $3 \ + WHERE id = 1", + self.admin_device_management, + self.disable_all_traffic, + self.only_client_activation, + ) + .execute(executor) + .await?; + + Ok(()) + } +} diff --git a/src/enterprise/db/models/mod.rs b/src/enterprise/db/models/mod.rs new file mode 100644 index 0000000000..742323f1da --- /dev/null +++ b/src/enterprise/db/models/mod.rs @@ -0,0 +1,2 @@ +pub mod enterprise_settings; +pub mod openid_provider; diff --git a/src/enterprise/db/models/openid_provider.rs b/src/enterprise/db/models/openid_provider.rs new file mode 100644 index 0000000000..099fd0f229 --- /dev/null +++ b/src/enterprise/db/models/openid_provider.rs @@ -0,0 +1,66 @@ +use model_derive::Model; +use sqlx::{query, query_as, Error as SqlxError, PgPool}; + +use crate::db::{Id, NoId}; + +#[derive(Deserialize, Model, Serialize)] +pub struct OpenIdProvider { + pub id: I, + pub name: String, + pub base_url: String, + pub client_id: String, + pub client_secret: String, +} + +impl OpenIdProvider { + #[must_use] + pub fn new>(name: S, base_url: S, client_id: S, client_secret: S) -> Self { + Self { + id: NoId, + name: name.into(), + base_url: base_url.into(), + client_id: client_id.into(), + client_secret: client_secret.into(), + } + } + + pub async fn upsert(self, pool: &PgPool) -> Result, SqlxError> { + if let Some(provider) = OpenIdProvider::::get_current(pool).await? { + query!( + "UPDATE openidprovider SET name = $1, base_url = $2, client_id = $3, client_secret = $4 WHERE id = $5", + self.name, + self.base_url, + self.client_id, + self.client_secret, + provider.id + ) + .execute(pool) + .await?; + + Ok(provider) + } else { + self.save(pool).await + } + } +} + +impl OpenIdProvider { + pub async fn find_by_name(pool: &PgPool, name: &str) -> Result, SqlxError> { + query_as!( + OpenIdProvider, + "SELECT id, name, base_url, client_id, client_secret FROM openidprovider WHERE name = $1", + name + ) + .fetch_optional(pool) + .await + } + + pub async fn get_current(pool: &PgPool) -> Result, SqlxError> { + query_as!( + OpenIdProvider, + "SELECT id, name, base_url, client_id, client_secret FROM openidprovider" + ) + .fetch_optional(pool) + .await + } +} diff --git a/src/enterprise/grpc/mod.rs b/src/enterprise/grpc/mod.rs new file mode 100644 index 0000000000..505916a0a5 --- /dev/null +++ b/src/enterprise/grpc/mod.rs @@ -0,0 +1 @@ +pub mod polling; diff --git a/src/enterprise/grpc/polling.rs b/src/enterprise/grpc/polling.rs new file mode 100644 index 0000000000..1bf3225667 --- /dev/null +++ b/src/enterprise/grpc/polling.rs @@ -0,0 +1,92 @@ +use sqlx::PgPool; +use tonic::Status; + +use crate::{ + db::{models::polling_token::PollingToken, Device, Id, User}, + enterprise::license::{get_cached_license, validate_license}, + grpc::{ + proto::{InstanceInfoRequest, InstanceInfoResponse}, + utils::build_device_config_response, + }, +}; + +pub struct PollingServer { + pool: PgPool, +} + +impl PollingServer { + #[must_use] + pub fn new(pool: PgPool) -> Self { + Self { pool } + } + + /// Checks validity of polling session + async fn validate_session(&self, token: &str) -> Result, Status> { + debug!("Validating polling token. Token: {token}"); + + // Polling service is enterprise-only, check the lincense + if validate_license(get_cached_license().as_ref()).is_err() { + debug!("No valid license, denying instance polling info"); + return Err(Status::failed_precondition("no valid license")); + } + + // Validate the token + let Some(token) = PollingToken::find(&self.pool, token).await.map_err(|err| { + error!("Failed to retrieve token: {err}"); + Status::internal("failed to retrieve token") + })? + else { + error!("Invalid token {token:?}"); + return Err(Status::permission_denied("invalid token")); + }; + + // Polling tokens are valid indefinitely + info!("Token validation successful {token:?}."); + + Ok(token) + } + + /// Prepares instance info for polling requests. Enterprise only. + pub async fn info(&self, request: InstanceInfoRequest) -> Result { + trace!("Polling info start"); + let token = self.validate_session(&request.token).await?; + let Some(device) = Device::find_by_id(&self.pool, token.device_id) + .await + .map_err(|err| { + error!("Failed to retrieve device id {}: {err}", token.device_id); + Status::internal("failed to retrieve device") + })? + else { + error!("Device id {} not found", token.device_id); + return Err(Status::internal("device not found")); + }; + debug!("Polling info for device: {}", device.wireguard_pubkey); + + // Ensure user is active + let Some(user) = User::find_by_device_id(&self.pool, device.id) + .await + .map_err(|err| { + error!("Failed to retrieve user for device id {}: {err}", device.id); + Status::internal("failed to retrieve user") + })? + else { + error!("User for device id {} not found", device.id); + return Err(Status::internal("user not found")); + }; + if !user.is_active { + warn!( + "Denying polling info for inactive user {}({:?})", + user.username, user.id + ); + return Err(Status::permission_denied("user inactive")); + } + + // Build & return polling info + let device_config = + build_device_config_response(&self.pool, &device.wireguard_pubkey, false).await?; + + Ok(InstanceInfoResponse { + device_config: Some(device_config), + }) + } +} diff --git a/src/enterprise/handlers/enterprise_settings.rs b/src/enterprise/handlers/enterprise_settings.rs new file mode 100644 index 0000000000..07442b9b31 --- /dev/null +++ b/src/enterprise/handlers/enterprise_settings.rs @@ -0,0 +1,49 @@ +use axum::{extract::State, http::StatusCode, Json}; +use serde_json::json; +use struct_patch::Patch; + +use super::LicenseInfo; +use crate::{ + appstate::AppState, + auth::{AdminRole, SessionInfo}, + enterprise::db::models::enterprise_settings::{EnterpriseSettings, EnterpriseSettingsPatch}, + handlers::{ApiResponse, ApiResult}, +}; + +pub async fn get_enterprise_settings( + session: SessionInfo, + State(appstate): State, +) -> ApiResult { + debug!( + "User {} retrieving enterprise settings", + session.user.username + ); + let settings = EnterpriseSettings::get(&appstate.pool).await?; + info!( + "User {} retrieved enterprise settings", + session.user.username + ); + Ok(ApiResponse { + json: json!(settings), + status: StatusCode::OK, + }) +} + +pub async fn patch_enterprise_settings( + _license: LicenseInfo, + _admin: AdminRole, + State(appstate): State, + session: SessionInfo, + Json(data): Json, +) -> ApiResult { + debug!( + "Admin {} patching enterprise settings.", + session.user.username, + ); + let mut settings = EnterpriseSettings::get(&appstate.pool).await?; + + settings.apply(data); + settings.save(&appstate.pool).await?; + info!("Admin {} patched settings.", session.user.username); + Ok(ApiResponse::default()) +} diff --git a/src/enterprise/handlers/mod.rs b/src/enterprise/handlers/mod.rs new file mode 100644 index 0000000000..fb5bba4837 --- /dev/null +++ b/src/enterprise/handlers/mod.rs @@ -0,0 +1,87 @@ +use crate::{ + auth::SessionInfo, + enterprise::license::validate_license, + handlers::{ApiResponse, ApiResult}, +}; + +pub mod enterprise_settings; +pub mod openid_login; +pub mod openid_providers; + +use axum::{ + async_trait, + extract::{FromRef, FromRequestParts}, + http::{request::Parts, StatusCode}, +}; + +use super::{db::models::enterprise_settings::EnterpriseSettings, license::get_cached_license}; +use crate::{appstate::AppState, error::WebError}; + +pub struct LicenseInfo { + pub valid: bool, +} + +/// Used to check if user is allowed to manage his devices. +pub struct CanManageDevices; + +#[async_trait] +impl FromRequestParts for LicenseInfo +where + S: Send + Sync, + AppState: FromRef, +{ + type Rejection = WebError; + + async fn from_request_parts(_parts: &mut Parts, _state: &S) -> Result { + let license = get_cached_license(); + + match validate_license(license.as_ref()) { + // Useless struct, but may come in handy later + Ok(()) => Ok(LicenseInfo { valid: true }), + Err(e) => Err(WebError::Forbidden(e.to_string())), + } + } +} + +pub async fn check_enterprise_status() -> ApiResult { + let license = get_cached_license(); + let valid = validate_license((license).as_ref()).is_ok(); + let license_info = license.as_ref().map(|license| { + serde_json::json!( + { + "valid_until": license.valid_until, + "subscription": license.subscription, + } + ) + }); + Ok(ApiResponse { + json: serde_json::json!({ "enabled": valid, + "license_info": license_info + }), + status: StatusCode::OK, + }) +} + +#[async_trait] +impl FromRequestParts for CanManageDevices +where + S: Send + Sync, + AppState: FromRef, +{ + type Rejection = WebError; + + /// Returns an error if current session user is not allowed to manage devices. + /// The permission is defined by [`EnterpriseSettings::admin_device_management`] setting. + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let appstate = AppState::from_ref(state); + let session = SessionInfo::from_request_parts(parts, state).await?; + let settings = EnterpriseSettings::get(&appstate.pool).await?; + if settings.admin_device_management && !session.is_admin { + Err(WebError::Forbidden( + "Only admin users can manage devices".into(), + )) + } else { + Ok(Self) + } + } +} diff --git a/src/enterprise/handlers/openid_login.rs b/src/enterprise/handlers/openid_login.rs new file mode 100644 index 0000000000..954ab2bed5 --- /dev/null +++ b/src/enterprise/handlers/openid_login.rs @@ -0,0 +1,442 @@ +use axum::{extract::State, http::StatusCode, Json}; +use axum_client_ip::{InsecureClientIp, LeftmostXForwardedFor}; +use axum_extra::{ + extract::{ + cookie::{Cookie, SameSite}, + CookieJar, PrivateCookieJar, + }, + headers::UserAgent, + TypedHeader, +}; +use openidconnect::{ + core::{ + CoreClient, CoreGenderClaim, CoreJsonWebKeyType, CoreJweContentEncryptionAlgorithm, + CoreJwsSigningAlgorithm, CoreProviderMetadata, CoreResponseType, + }, + reqwest::async_http_client, + AuthenticationFlow, ClientId, ClientSecret, CsrfToken, EmptyAdditionalClaims, IdToken, + IssuerUrl, Nonce, ProviderMetadata, RedirectUrl, Scope, +}; +use serde_json::json; +use sqlx::PgPool; +use time::Duration; + +use super::LicenseInfo; +use crate::{ + appstate::AppState, + db::{MFAInfo, Session, SessionState, Settings, User, UserInfo}, + enterprise::db::models::openid_provider::OpenIdProvider, + error::WebError, + handlers::{ + user::{check_username, prune_username}, + ApiResponse, AuthResponse, SESSION_COOKIE_NAME, SIGN_IN_COOKIE_NAME, + }, + headers::{check_new_device_login, get_user_agent_device, parse_user_agent}, + server_config, +}; + +type ProvMeta = ProviderMetadata< + openidconnect::EmptyAdditionalProviderMetadata, + openidconnect::core::CoreAuthDisplay, + openidconnect::core::CoreClientAuthMethod, + openidconnect::core::CoreClaimName, + openidconnect::core::CoreClaimType, + openidconnect::core::CoreGrantType, + openidconnect::core::CoreJweContentEncryptionAlgorithm, + openidconnect::core::CoreJweKeyManagementAlgorithm, + openidconnect::core::CoreJwsSigningAlgorithm, + openidconnect::core::CoreJsonWebKeyType, + openidconnect::core::CoreJsonWebKeyUse, + openidconnect::core::CoreJsonWebKey, + openidconnect::core::CoreResponseMode, + openidconnect::core::CoreResponseType, + openidconnect::core::CoreSubjectIdentifierType, +>; + +async fn get_provider_metadata(url: &str) -> Result { + let issuer_url = IssuerUrl::new(url.to_string()).unwrap(); + + // Discover the provider metadata based on a known base issuer URL + // The url should be in the form of e.g. https://accounts.google.com + // The url shouldn't contain a .well-known part, it will be added automatically + let Ok(provider_metadata) = + CoreProviderMetadata::discover_async(issuer_url, async_http_client).await + else { + return Err(WebError::Authorization(format!( + "Failed to discover provider metadata, make sure the providers' url is correct: {url}", + ))); + }; + + Ok(provider_metadata) +} + +async fn make_oidc_client(pool: &PgPool) -> Result { + let Some(provider) = OpenIdProvider::get_current(pool).await? else { + return Err(WebError::ObjectNotFound( + "OpenID provider not set".to_string(), + )); + }; + + let provider_metadata = get_provider_metadata(&provider.base_url).await?; + let client_id = ClientId::new(provider.client_id); + let client_secret = ClientSecret::new(provider.client_secret); + let config = server_config(); + let url = format!("{}auth/callback", config.url); + let redirect_url = match RedirectUrl::new(url) { + Ok(url) => url, + Err(err) => { + error!("Failed to create redirect URL: {err:?}"); + return Err(WebError::Authorization( + "Failed to create redirect URL".to_string(), + )); + } + }; + + Ok( + CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret)) + .set_redirect_uri(redirect_url), + ) +} + +pub async fn get_auth_info( + _license: LicenseInfo, + private_cookies: PrivateCookieJar, + State(appstate): State, +) -> Result<(PrivateCookieJar, ApiResponse), WebError> { + let client = make_oidc_client(&appstate.pool).await?; + + // Generate the redirect URL and the values needed later for callback authenticity verification + let (authorize_url, csrf_state, nonce) = client + .authorize_url( + AuthenticationFlow::::Implicit(false), + CsrfToken::new_random, + Nonce::new_random, + ) + .add_scope(Scope::new("email".to_string())) + .add_scope(Scope::new("profile".to_string())) + .url(); + + let config = server_config(); + let nonce_cookie = Cookie::build(("nonce", nonce.secret().clone())) + .domain( + config + .cookie_domain + .clone() + .expect("Cookie domain not found"), + ) + .path("/api/v1/openid/callback") + .http_only(true) + .same_site(SameSite::Strict) + .secure(true) + .max_age(Duration::days(1)) + .build(); + let csrf_cookie = Cookie::build(("csrf", csrf_state.secret().clone())) + .domain( + config + .cookie_domain + .clone() + .expect("Cookie domain not found"), + ) + .path("/api/v1/openid/callback") + .http_only(true) + .same_site(SameSite::Strict) + .secure(true) + .max_age(Duration::days(1)) + .build(); + + let private_cookies = private_cookies.add(nonce_cookie).add(csrf_cookie); + + Ok(( + private_cookies, + ApiResponse { + json: json!( + { + "url": authorize_url, + } + ), + status: StatusCode::OK, + }, + )) +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct AuthenticationResponse { + id_token: IdToken< + EmptyAdditionalClaims, + CoreGenderClaim, + CoreJweContentEncryptionAlgorithm, + CoreJwsSigningAlgorithm, + CoreJsonWebKeyType, + >, + state: CsrfToken, +} + +pub async fn auth_callback( + _license: LicenseInfo, + cookies: CookieJar, + private_cookies: PrivateCookieJar, + user_agent: Option>, + forwarded_for_ip: Option, + InsecureClientIp(insecure_ip): InsecureClientIp, + State(appstate): State, + Json(payload): Json, +) -> Result<(CookieJar, PrivateCookieJar, ApiResponse), WebError> { + debug!("Auth callback received, logging in user..."); + + // Get the nonce and csrf cookies, we need them to verify the callback + let mut private_cookies = private_cookies; + let cookie_nonce = private_cookies + .get("nonce") + .ok_or(WebError::Authorization( + "Nonce cookie not found".to_string(), + ))? + .value_trimmed() + .to_string(); + let cookie_csrf = private_cookies + .get("csrf") + .ok_or(WebError::BadRequest("CSRF cookie not found".to_string()))? + .value_trimmed() + .to_string(); + + // Verify the csrf token + if *payload.state.secret() != cookie_csrf { + return Err(WebError::Authorization("CSRF token mismatch".to_string())); + }; + + // Get the ID token and verify it against the nonce value received in the callback + let client = make_oidc_client(&appstate.pool).await?; + let nonce = Nonce::new(cookie_nonce); + let token_verifier = client.id_token_verifier(); + let id_token = payload.id_token; + + private_cookies = private_cookies + .remove(Cookie::from("nonce")) + .remove(Cookie::from("csrf")); + + // claims = user attributes + let token_claims = match id_token.claims(&token_verifier, &nonce) { + Ok(claims) => claims, + Err(error) => { + return Err(WebError::Authorization(format!( + "Failed to verify ID token, error: {error:?}", + ))); + } + }; + + // Only email and username is required for user lookup and login + let email = token_claims.email().ok_or(WebError::BadRequest( + "Email not found in the information returned from provider.".to_string(), + ))?; + + // Try to get the username from the preferred_username claim, if it's not there, extract it from the email + let username = if let Some(username) = token_claims.preferred_username() { + let mut username: String = username.to_string(); + username = prune_username(&username); + // Check if the username is valid just in case, not everything can be handled by the pruning + check_username(&username)?; + username + } else { + // Extract the username from the email address + let username = email.split('@').next().ok_or(WebError::BadRequest( + "Failed to extract username from email address".to_string(), + ))?; + let username = prune_username(username); + // Check if the username is valid just in case, not everything can be handled by the pruning + check_username(&username)?; + username + }; + + // Get the sub claim from the token + let sub = token_claims.subject().to_string(); + + // Handle logging in or creating the user + let settings = Settings::get_settings(&appstate.pool).await?; + let user = match User::find_by_sub(&appstate.pool, &sub).await { + Ok(Some(user)) => { + debug!( + "User {} is trying to log in using an OpenID provider.", + user.username + ); + // Make sure the user is not disabled + if !user.is_active { + debug!("User {} tried to log in, but is disabled", user.username); + return Err(WebError::Authorization("User is disabled".to_string())); + } + user + } + Ok(None) => { + if let Some(mut user) = User::find_by_email(&appstate.pool, email).await? { + // User with the same email already exists, merge the accounts + info!( + "User with email address {} is logging in through OpenID Connect for the first time and we've found an existing account with the same email address. Merging accounts.", + user.email + ); + user.openid_sub = Some(sub); + user.save(&appstate.pool).await?; + user + } else { + // Check if the user should be created if they don't exist (default: true) + if settings.openid_create_account { + info!( + "User {} is logging in through OpenID Connect for the first time and there is no account with the same email address ({}). Creating a new account.", + username, email.as_str() + ); + // Check if user with the same username already exists + // Usernames are unique + if User::find_by_username(&appstate.pool, &username) + .await? + .is_some() + { + return Err(WebError::Authorization(format!( + "User with username {username} already exists" + ))); + } + + // Extract all necessary information from the token needed to create an account + let given_name_error = + "Given name not found in the information returned from provider."; + let given_name = token_claims + .given_name() + .ok_or(WebError::BadRequest(given_name_error.to_string()))? + // 'None' gets you the default value from a localized claim. Otherwise you would need to pass a locale. + .get(None) + .ok_or(WebError::BadRequest(given_name_error.to_string()))?; + let family_name_error = + "Family name not found in the information returned from provider."; + let family_name = token_claims + .family_name() + .ok_or(WebError::BadRequest(family_name_error.to_string()))? + .get(None) + .ok_or(WebError::BadRequest(family_name_error.to_string()))?; + let phone = token_claims.phone_number(); + + let mut user = User::new( + username.to_string(), + None, + family_name.to_string(), + given_name.to_string(), + email.to_string(), + phone.map(|v| v.to_string()), + ); + user.openid_sub = Some(sub); + user.save(&appstate.pool).await? + } else { + warn!( + "User with email address {} is trying to log in through OpenID Connect for the first time, but the account creation is disabled. They should perform an enrollment first.", + email.as_str() + ); + return Err(WebError::Authorization( + "User not found. The user needs to be created first in order to login using OIDC.".to_string(), + )); + } + } + } + Err(e) => { + return Err(WebError::Authorization(e.to_string())); + } + }; + + // Handle creating the session + let ip_address = forwarded_for_ip.map_or(insecure_ip, |v| v.0).to_string(); + let user_agent_string = match user_agent { + Some(value) => value.to_string(), + None => String::new(), + }; + let agent = parse_user_agent(&appstate.user_agent_parser, &user_agent_string); + let device_info = agent.clone().map(|v| get_user_agent_device(&v)); + Session::delete_expired(&appstate.pool).await?; + let session = Session::new( + user.id, + SessionState::PasswordVerified, + ip_address.clone(), + device_info, + ); + session.save(&appstate.pool).await?; + let max_age = Duration::seconds(server_config().auth_cookie_timeout.as_secs() as i64); + let config = server_config(); + let auth_cookie = Cookie::build((SESSION_COOKIE_NAME, session.id.clone())) + .domain( + config + .cookie_domain + .clone() + .expect("Cookie domain not found"), + ) + .path("/") + .http_only(true) + .secure(!config.cookie_insecure) + .same_site(SameSite::Lax) + .max_age(max_age); + let cookies = cookies.add(auth_cookie); + let login_event_type = "AUTHENTICATION".to_string(); + + info!("Authenticated user {username} with external OpenID provider."); + if user.mfa_enabled { + debug!("User {username} has MFA enabled, sending MFA info for further authentication."); + if let Some(mfa_info) = MFAInfo::for_user(&appstate.pool, &user).await? { + check_new_device_login( + &appstate.pool, + &appstate.mail_tx, + &session, + &user, + ip_address, + login_event_type, + agent, + ) + .await?; + Ok(( + cookies, + private_cookies, + ApiResponse { + json: json!(mfa_info), + status: StatusCode::CREATED, + }, + )) + } else { + error!("Couldn't fetch MFA info for user {username} with MFA enabled"); + Err(WebError::DbError("MFA info read error".into())) + } + } else { + debug!("User {username} has MFA disabled, returning user info for login."); + let user_info = UserInfo::from_user(&appstate.pool, &user).await?; + + check_new_device_login( + &appstate.pool, + &appstate.mail_tx, + &session, + &user, + ip_address, + login_event_type, + agent, + ) + .await?; + + if let Some(openid_cookie) = private_cookies.get(SIGN_IN_COOKIE_NAME) { + debug!("Found openid session cookie, returning the redirect URL stored in the cookie."); + let redirect_url = openid_cookie.value().to_string(); + Ok(( + cookies, + private_cookies.remove(openid_cookie), + ApiResponse { + json: json!(AuthResponse { + user: user_info, + url: Some(redirect_url) + }), + status: StatusCode::OK, + }, + )) + } else { + debug!("No OpenID session found, proceeding with login to defguard."); + Ok(( + cookies, + private_cookies, + ApiResponse { + json: json!(AuthResponse { + user: user_info, + url: None, + }), + status: StatusCode::OK, + }, + )) + } + } +} diff --git a/src/enterprise/handlers/openid_providers.rs b/src/enterprise/handlers/openid_providers.rs new file mode 100644 index 0000000000..d7d170fbb8 --- /dev/null +++ b/src/enterprise/handlers/openid_providers.rs @@ -0,0 +1,170 @@ +use axum::{ + extract::{Path, State}, + http::StatusCode, + Json, +}; +use serde_json::json; + +use super::LicenseInfo; +use crate::{ + appstate::AppState, + auth::{AdminRole, SessionInfo}, + enterprise::db::models::openid_provider::OpenIdProvider, + handlers::{ApiResponse, ApiResult}, +}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct AddProviderData { + name: String, + base_url: String, + client_id: String, + client_secret: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DeleteProviderData { + name: String, +} + +impl AddProviderData { + #[must_use] + pub fn new(name: &str, base_url: &str, client_id: &str, client_secret: &str) -> Self { + Self { + name: name.to_string(), + base_url: base_url.to_string(), + client_id: client_id.to_string(), + client_secret: client_secret.to_string(), + } + } +} + +pub async fn add_openid_provider( + _license: LicenseInfo, + _admin: AdminRole, + session: SessionInfo, + State(appstate): State, + Json(provider_data): Json, +) -> ApiResult { + // Currently, we only support one OpenID provider at a time + let new_provider = OpenIdProvider::new( + provider_data.name, + provider_data.base_url, + provider_data.client_id, + provider_data.client_secret, + ) + .upsert(&appstate.pool) + .await?; + debug!( + "User {} adding OpenID provider {}", + session.user.username, new_provider.name + ); + info!( + "User {} added OpenID client {}", + session.user.username, new_provider.name + ); + + Ok(ApiResponse { + json: json!({}), + status: StatusCode::CREATED, + }) +} + +pub async fn get_current_openid_provider( + _license: LicenseInfo, + _admin: AdminRole, + State(appstate): State, +) -> ApiResult { + match OpenIdProvider::get_current(&appstate.pool).await? { + Some(provider) => Ok(ApiResponse { + json: json!(provider), + status: StatusCode::OK, + }), + None => Ok(ApiResponse { + json: json!({}), + status: StatusCode::NOT_FOUND, + }), + } +} + +pub async fn delete_openid_provider( + _license: LicenseInfo, + _admin: AdminRole, + session: SessionInfo, + State(appstate): State, + Path(provider_data): Path, +) -> ApiResult { + debug!( + "User {} deleting OpenID provider {}", + session.user.username, provider_data.name + ); + let provider = OpenIdProvider::find_by_name(&appstate.pool, &provider_data.name).await?; + if let Some(provider) = provider { + provider.delete(&appstate.pool).await?; + info!( + "User {} deleted OpenID provider {}", + session.user.username, provider_data.name + ); + Ok(ApiResponse { + json: json!({}), + status: StatusCode::OK, + }) + } else { + warn!( + "User {} failed to delete OpenID provider {}. Such provider does not exist.", + session.user.username, provider_data.name + ); + Ok(ApiResponse { + json: json!({}), + status: StatusCode::NOT_FOUND, + }) + } +} + +pub async fn modify_openid_provider( + _license: LicenseInfo, + _admin: AdminRole, + session: SessionInfo, + State(appstate): State, + Json(provider_data): Json, +) -> ApiResult { + debug!( + "User {} modifying OpenID provider {}", + session.user.username, provider_data.name + ); + let provider = OpenIdProvider::find_by_name(&appstate.pool, &provider_data.name).await?; + if let Some(mut provider) = provider { + provider.base_url = provider_data.base_url; + provider.client_id = provider_data.client_id; + provider.client_secret = provider_data.client_secret; + provider.save(&appstate.pool).await?; + info!( + "User {} modified OpenID client {}", + session.user.username, provider.name + ); + Ok(ApiResponse { + json: json!({}), + status: StatusCode::OK, + }) + } else { + warn!( + "User {} failed to modify OpenID client {}. Such client does not exist.", + session.user.username, provider_data.name + ); + Ok(ApiResponse { + json: json!({}), + status: StatusCode::NOT_FOUND, + }) + } +} + +pub async fn list_openid_providers( + _license: LicenseInfo, + _admin: AdminRole, + State(appstate): State, +) -> ApiResult { + let providers = OpenIdProvider::all(&appstate.pool).await?; + Ok(ApiResponse { + json: json!(providers), + status: StatusCode::OK, + }) +} diff --git a/src/enterprise/license.rs b/src/enterprise/license.rs new file mode 100644 index 0000000000..74b23f28a3 --- /dev/null +++ b/src/enterprise/license.rs @@ -0,0 +1,741 @@ +use std::{ + sync::{RwLock, RwLockReadGuard}, + time::Duration, +}; + +use anyhow::Result; +use base64::prelude::*; +use chrono::{DateTime, TimeDelta, Utc}; +use humantime::format_duration; +use pgp::{types::KeyTrait, Deserializable, SignedPublicKey, StandaloneSignature}; +use prost::Message; +use sqlx::{error::Error as SqlxError, PgPool}; +use thiserror::Error; +use tokio::time::sleep; + +use crate::{db::Settings, VERSION}; + +const LICENSE_SERVER_URL: &str = "https://pkgs.defguard.net/api/license/renew"; + +static LICENSE: RwLock> = RwLock::new(None); + +pub fn set_cached_license(license: Option) { + *LICENSE + .write() + .expect("Failed to acquire lock on the license mutex.") = license; +} + +pub fn get_cached_license() -> RwLockReadGuard<'static, Option> { + LICENSE + .read() + .expect("Failed to acquire lock on the license mutex.") +} + +tonic::include_proto!("license"); + +#[cfg(not(test))] +pub(crate) const PUBLIC_KEY: &str = "-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGbQi9EBEAC7eeWSO6xN3nJC1axoySCrBzj6sbausKVW8opkGI3zRJ3hT6Bg +yXkNm/DyPN1r3yRtkz49PrWtk2dmZHI65Zi/SRN9NyWAfwqMg/GFkL9TOPAUokAq +H5nmkA333maIOl2GMorq3hrLYJbkFP0U3UJ7Sp6MUxOejNhYFCg1h/5ibopUtDpA +pIBv14vKtAzTATvsGdU8yT4ryr7VkatsF3FU76vbc6SwdRZLiBGYO2OfnFawIh3V +hjUJbegUHBZfpfaLpznuNYnjhuzy4oUchBOagBh7WhrIR7a+IQbt+wwDjMK6O+1P +iCtYVWYXKBYpTUjvNdeyzjXpSpQKTLq1ZDdjHFX1IjikR74EZNMG8LAJcLM/5OaF +LOUJPDE5K7Axq0zDi5kJmltiBqlaeszgWVGXysXeaAVKwk2GNiQfb8q/QX1P6asA +0NYAIk8p5VK8Vmb7eQvK7HKTh2WSZLDfuDEKlvW6987H9+TeqkHhSTq7aO2TjIkW +KXRvN5oH0m798JKr5tcuHvZDX7EJtRR8HQ//71ttLMeOGOQbjtPM+qFtDX8wPRm2 +vPKPZMc7gPGn+OB2e8Vb2k1irDZszLv2TieofWkaIlEkz2EuGapVkM0pJg47L0Tr +wwb+KShUGTFreOswXzNX9bPPKpVOrnmEhr9NlP3TcN4LRQJipE+fvgIRhwARAQAB +tC5kZWZndWFyZCBsaWNlbnNlIHNlcnZlciA8bGljZW5zZUBkZWZndWFyZC5uZXQ+ +iQJOBBMBCgA4FiEEd3NOssz+EdwiWubqUswQzCl98KYFAmbQjY0CGwEFCwkIBwIG +FQoJCAsCBBYCAwECHgECF4AACgkQUswQzCl98KZd/w//chxENfSt2YaWPwyCWcpy +KUUjBN5pT0A0NAAsV044uOh6PXPJ6zf2sQxOpKWMx6F+FVlcOqQDmwOSNoDoofJB +DezLcxfKhL66HxWXZpCY1zSYIRpsBRHTA+rjJv/cHnSKiF+Ie+qHrtWshXc0Zvk4 +PtTHUFqxAdWN3crKViSFfuLqRXMKdJQKHh3iZVDOaaK6VluJAnhBTU5OS1EMTuzg +MFmv2ekFkdWS3zoPcfZRmbj5J2/1gT1SgoG22BOFiEYKkNZducgYZ5oefIPk18v0 +TjHfecZdyg2JmwAxL65QH6OWBENywTJz8yopITbywTaASqFRIdUCX0Ls5CMm3gbK +vvzMmrZE40cVxmvZptBDmo4gE0W1lNTlnTxqA2rA1crok1Wa8nKIVVQP/lxa49os +5kSbIURTT6llwAFOrlBqO6KH3Ngt7CeQZ4UfjbCIcYEu9r0/POdoGfaJs0ljMnFu +NMnPmk1b1UJYHBjNy1JHKuPOUh4UISN9CDwJALVopxqmx7EENwRYhLxGyK1VNOQo +pHHdDqG+r8JngxNhdrkSHd0s9nBGWjZ1DWffJWEHUwILRX6SHAPLr2tH1KJ0c16Y +aOmZSJeWATWL8ZxFyXYh2L5q7SiU+7PyzFySsNRz0ZB2lBVIOGQOjLD+jiU63G/P +t5IR2KoQ13E0+MegjE8jJiG0LWRlZmd1YXJkIGVudGVycHJpc2UgPGVudGVycHJp +c2VAZGVmZ3VhcmQubmV0PokCTgQTAQoAOBYhBHdzTrLM/hHcIlrm6lLMEMwpffCm +BQJm0IvRAhsBBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEFLMEMwpffCmOOQQ +AK9BW2ETgaSa/b2fFSA56wWkUnZ4BNYYLF7Fvv5Y40Bs+vsUlvNdRgBQF0aJv9SP +m6u+3rqx66CDdncG5T6/BcdBmcmEjvymXGPgLeJ/e9GO1egNrM0aMIbqDglTPkcc +7CcisjxeF6GCRljD+x/ApnzPrpdaeszLVfRrZqyy/pawahmGI8wgBZapvHOUZyeq +Pci3RVV5I2QjAkle9/k9mBevXpBGhv4PAY8ZzlM60Xli7yWbqh4XAgJEFpH7cXXc +LMHgx37XcR6wJPQVfpeEuWyedQOPdNGMKQR8wdq7mrcWMEmP6cGGyibQW8TRn+Iu +Ei7t/PqYqLp+baReNolPzUdEqk5IwKHZjV9a3DhuTNysutMliEeFBFyQYtk7wZsA +ClwR2wsyJiMdsWpC1jQbtz5e4OZNP96K+mH/dxp7K+TdyX4mTg0uWqUuk4jUAxAh +zI8CwyK7sqZXGPt31tWPxoIt6SnyDssPZ2c32q6YR8jb/C8aF3o1rqxPo7aOqV2I +Px9F1+IUI1i8tqj23a697upFCcjfyARLray38JkaO9F8o1EhAwr04YA+XDbQyV6g +pZbkvmjPsVFPT9DjKArAqdIgSreLv5tjcTsxzXfbv1GmsXNTwSbPHuUMGXcCeWym +8aktXWM4+jrylRQvUNbAFJHmsXgiWVoqpe0gaxjIDFcTtDFkZWZndWFyZCBzdWJz +Y3JpcHRpb24gPHN1YnNjcmlwdGlvbkBkZWZndWFyZC5uZXQ+iQJOBBMBCgA4FiEE +d3NOssz+EdwiWubqUswQzCl98KYFAmbQjhcCGwEFCwkIBwIGFQoJCAsCBBYCAwEC +HgECF4AACgkQUswQzCl98KYxBA/9GmCqitXmajxc01k0g5xXUBvPcl9D9Mb70c0t +J4dI/9/wrp9ZwKh2p93A2BZjphkahIHsAiqCjB1hjwJbABKnAISztkGLOkJEUKEq +UEVYdDEyEC2H8hP6ub7Zch2rPZa8TuGWh1hFB59/Cr5P6MTmUABW6zSoeN1otaUo +tqLaNHoI/IsY2fP4VanmSyA6vOT2cYjZVKxqDQjbCft1NnCdbmTiDG11cqRATTQ1 ++fyW7ygCfBSrkXqQuoeYlxX6joPW8nm2OoDA5f/TeqjFj8y5b/Wi6SlVnbOSSM8K +AluYD/XvFP+iPzKXJZXinN6M8Cbe9OIFlICZUzd31RPzpCK/rw68BhT1ikydBSYk +x73lT7G8fjRm3jviX8hXJ6qzcsSyfAe/GKMxG/TqcDvlW98O9BB3jih2r1+5FubT +QYCD7KsiaveDzy9SU3Wiah2zoJTuP80uFwSYgC1krkhpzjtUIohpqVH/KLlKUPTp +E/40S4rTyf+eAg76HegBt83AxABLpduFSufcgJ0S8A8/Z7/Aa3/Nx+MjMtdusKOP +UlnOrQ4g41/Cox4Q8sZCjsi1pz/upqT92bDqixmT7KU79OE4AYYJ7uJ1J5AzRIit +jzxLRvFAJcZUS/TBEgYlXosJ32gci08TuoNKUKxhFKI1rwQvPvlhAOIaJkMmlHld +YhDCAa65Ag0EZtCMBQEQAPJ3JQZTskkqdswT22vUUJEPba8Fxb3nHjGRDesTMx7j +uEADCjAT4k1iqsUIsTy6L3SX74k0dHssc/zCL5aIFCPakPtgcKZnQqT+Eh3kh4/T ++TQFMThqUMRYpkQoNLURJQd6X6kZLpcry+IlbwTNEMqdqNeVGM5PCN8Kyt+Q+Zbt +SauoB9E7XMRhIhAnR5kuTDEOHKOW9wajFhC9swNR/ZHZH5GNwXPI5SGSGsqHzBnN +VE6Y94+fvCdcqVwkJ0uMkO6AXg7/kHjlyMXQirouRhRK2HnzqTsK84ER0NwH+ACT +aGa/ySkjIaF6svn0vLapjPcQKivHilTJubO8lHTMqD27VAX0Jwm+dbfPd8b1vwvQ +TJwIvbRlq3x+vSBr4HkBaKzcqg81SYHoZcVoAkOh3BvkBMAViuyXY8KUncqmLamt +wJhN0lt32dZ3121WnIEUGSSpJ4/FlQ3XUiBwxUUV/Q4wol+Cyyx5QWJWPnR/0kya +qVj1gLT0RFZOlqf58/garcDRcF8cahGLb/6ypb4PsL4wd+KX0cfFQclG2Z1E9YMl +/XDjfw55oeIO4evLFSWKQJg8lH8u0pxdXKsBi034K/kxgYzz1wCRHjeRRCL5mm6V +QB8C/6kcnGsQ43v/62eSXQu2wAiuyJK+JMfUwGGCfWAhb5mGQgOVCu6ZXYolTA2f +ABEBAAGJBHIEGAEKACYWIQR3c06yzP4R3CJa5upSzBDMKX3wpgUCZtCMBQIbAgUJ +EswDAAJACRBSzBDMKX3wpsF0IAQZAQoAHRYhBFVYRcjAloCvOmdJ42ryZ3P4wdRE +BQJm0IwFAAoJEGryZ3P4wdRE4wsP/jvKrJlW5jLQxITA7uLOfWCE6+HfSKZZ8a+w +v4mRaEI/fBcPif2SBqrTOgfjMZi02HoDFJROzx+IEwegK2DQxjCDjUOyw+fhrIGW +9EAcYhjki1DF/IFs/vioZ/oJoQDQnZ36n28sG8mB3YNwABGPOqRThVBxitDD0tfC +RJxHtCHD/g54t2nSIxh0stFca0sF5u9OyNNAggBBWOHUxGehjhRR4Blp0ByHaqxK +k2DX93rIHr1Dbjz5nAX74Ok0ugATNB11L4MmHe4zNkqsraUFJO/8Gk7Y6sSd/9hm +xywYDimKvyb/NIjKUINa48YjGFhX6rQYLcgPRrkWPmA6rgu+arfgRm7empatH65R +hxJoRHsbDsQcnY+aUVoiuFa8LSIDVnq1xTDO1+y+ZwmCCkEXUpnFubfNsrrOMKmp +qUI1GYezyX/0ZlYVtwEE7B6iEsiR1UD3OUz+inwMBw3QJ5s4q3xb1hldj1tmlDKW ++TxMV0gs7k/zReGEQBteEr/63HCYAIrzU3gMLGuzfh50KYQ2Q/CNHd4aWYLg+GmK +DW1IDhs5Rfd9EoRm6rUbAf4x2mo6Y8IzFIZBOlDNOq9RJP0y7vg7ZsRyhtd1h80N +H70ieJHiaXhQlc9OYmD4/Hsmz0TIWOhcYDRljcWfO/GUHwVK+Ttfpy3Jq6tWMNoz +0VefiztSwkUP/R2EBbpoIIk/IcuD+lkYtVJ8XiXVJF77GkD0dZRyvd6V6j4hJ8zR +EPQu+Lp23S4V5g+nu+b2o9SnFQ8zl4v6UAtJoCOc79C8B/xT1jVCYDPscuu3B9oh +ovH9Sqr6tQwwri2D2PnBpZfd8U5PpkByJkv8VvVybzEb4gKVfrh4zlDXYpyFBl1/ +ZLfcORnvcUnPQ/qHGBPCdrJUfFPuhO+QAmW5btTfPyQy+bMOfF3gyKl4ER1PhHJe +l3Rxr5kl/7FfuiOfJ6IQQYkda87DLn5L838byIYmlU33IRhG4i2Q6mHbPcoNq/AR +gR0p8cGgmTzZBXYtJd/03olYVKigDZhaUeaGLOVuEYrYoR9EtkCBeQVqkX6kAhG0 +xVLjwx3IzNRfqwnrgA2VnQUmJB6EhmVTNohkr66LVz5WJdO7wDoO3Do9vH9rqSa9 +vHoqp93zqoXULkFt5W5snJsxDGo407T/d69sjrNqW/Xzk6LIW4rFuWI3ea0kBXjb +J+huDfRNOnSqiU3G6ojjNRvxUe+P/KTKfoJERCWMhTWjsaCWY+rHNCHkRxUHktdb +9q4owlFpwwFoC8ga/eGd1PI9MfVWfC4YgHtuZ1YlNVNWb11zWDoIqxd053lHgCLx +HpSHlBlRoLiwOpqmU7fnwl7QNvrmm2gvbMTqXvnrxN9D4WIvZF02uxOPuQINBGbQ +jAcBEADz36Q9hHlmppoV9YTrZ2N4cDXz8qyG5F1q8CMRi2LCgqTfKeCxnD49Y1xz +280jPq4yDpGWJSeGyH/PTpceveVowaMBGnwmtFVN6375oNR28NLzR0wmw5O9Fj01 +xXiwot6ft2hkJs9zQRcecydEOnlX+vgauGFU4f7ZYKnPQgiyTlA1OFRa1hqy5iZA +mTxuVT7w6ZZ2laB3qsSuQK7FGN3NCC6Rnn7Wka4GrSkO5dSnMFrsGagTCHtbGura +GGesUuk9+37n7XWprLgnUMvZPRogyfu7cH/teQv8E5dyHSYQd7oWipNQt3GeoSIW +bFAWft+JsuCwMlmb4uoF/JlVvDVIdUsfa1ISPA5PJZorfLhnmNscp8LL0DvS2x6i +HXDjOzRAszkJOhCqDwcodtdtMIokaMnCTY08R4C40T3eT1uq6Yvvih9j/0bCx56x +7MRvMjcGS5qgNKYnibpthGYPaOIG4qWpBaDGI01iZiw15pyU0DZis1NtL7IpYLVR +/R359IMqt0/n6ynM5UoyEcTYas5u1DXpXPlItHg1LLQM9JmCyo2+loropUBRCJ4W +R/qUQHJwW8YLNp6q0EvdGYaFxZNejs0T2b+eiJeFPDvyiEYgcnQIrR6wujjIyy1N +ysTli/wGJ9VZG8pAZd2+TaK79/6FsH25RlDiUAvUSu8BcH5m8QARAQABiQI8BBgB +CgAmFiEEd3NOssz+EdwiWubqUswQzCl98KYFAmbQjAcCGwwFCRLMAwAACgkQUswQ +zCl98KavWBAAl1K2FpnDfR/sco7s1+Twq14BDBkJvaz7bnQ/4ZASf6qT7uhiy74e +5SGIWY1JN/KXPR43DwtUUYJUVu4XETZeWezqJ1YUg0z0eAL00vqczmQudyMD0aaR +V18sNLypmfui0i3meXW6QRXe6I0D1GUMoia6R51sg1TRDs2TYDQem0ZDq27igEAF +bTfLt3VkcOAKQL9bEDdH8VR95XG4IjkdjbdYnhBiwo6XLxvwh1469KCXrSbLEUeI +bmi9ISJynYUxBJpqmBvT1j+2p8RewtoR0OCYM7AkNJkyjqxOTwr/Q6LKosuEbNrm +YuGOl08DPG1bNfTBHw7fB7joT3iXAYj3SYb7XNbinF0bTzpfEO5HmBWobTStimP+ +GVYydAMlNKS36GykQblIAdNzA132dz9wDtEWOd8+jANSF1hK2dzmBb/YdQOAIbi/ +AN95Z/jqwKGCrCQ5n8kjFKCB8H9OD0hJrgeG7l+1bcqXpv3tf6ryKjsDvNyi/0He +YarZNHOTqGd8r38K17yONsjXkcylx2qvMunB+wfbu6e9hHkDC4NJLWV9lFLCLJLy +O2o7u6r32xUXEV3g8TKH5vHk+0gACSErR0OfjNcMpY+oZoDW3fFThGwbBUm5vNNF +6VyJJn+mhW0zllGSTLmUdpmNbJE9qBltQjbX4XmvBen2UUAn1ue9PGu5Ag0EZtCM +CAEQAK386TeL7ltMga+pQtTWeiv6pTubCLMlmtLv7X1vkZEGEj1rxEzia6HSMdYd +lIvNX0C1+8Hm2XaZoSHR5BXGO9xluYCLFvIjmEiRRwZcjg50+Y1bpCHB6S0cx5PX +YLopRRb9I4wpqkvqc975XSjVo8KkCcoKeXlZe1h0mf055pCe4Kpkwgr60n8oayEu +Qqfed2wNuIfSX/28KOpTJRNk9k/3OJ+b9r/tH8zBYow+cRX7raXelPPiteO5NeNN +0WtE/pS1PlEAsHNnCf13InmEdgQR4EkyHS3dVF1MmRrhK6Uwq46vMFmI7LB21hNm +UwKZoMHpE3UGP1C1XKizOckDIq6xlqm+PzxnO0u4OJ8RzL85d8MoWRMVILvGptnQ +4xHu8DdiR7J4qnufq3YFibaKvHEQVcSbnu4IerH2zbMV4g23fjoHQo68eWn5TEyV +Kk7wVSfPiHb7j27yaKXSiQgVhNBMignnj6fJUux+iq6Mer+5OW7Z9Ihip0ZT9DfJ +N9trWDxOe9hyXvPd2C0n0paHQYygnmZ0mkZhGGvIIraRY2qbGWY2QQFHDTY6B9Lq +Zo8Ap0dsLGostYyHnVs/h+vb7/cgEoESwWFhv1eSFimHOG9zgTIM7ksbRLRQxyus +5GzorNnfV2p/ZsAU6ddBt+nZsyTg+wE0cLx0QnHPW62EDPrXABEBAAGJAjwEGAEK +ACYWIQR3c06yzP4R3CJa5upSzBDMKX3wpgUCZtCMCAIbIAUJEswDAAAKCRBSzBDM +KX3wplZwEACtaSNkm+h1K9qGH2Y2YMhjd9bnWvkq1l7LkMEfYStUF7fkoF89xxUh +uOs6APqaOXn95iXPRTW35MGk6LaPRyVDvq+dcCxvrx19yc3M99eTAmv2Q7Spweo3 +bptXgGaQ3PrGJUsD40QK5K/VDxyMCg7kHz4mKqaTcW7J7UN/GMpVhsRINxCNAKgn +qivttO1qsryooeXbZ4/Mz8a9M6nbn+W+CDft4dvtsPuoTkxH6x/cY0gAFuK8r1sE +7y9MNajX+0YlhtjtIsfkYrBIGaMPFb2xfTKX3ECXbqMJfgL6kwZ2lrFx8W4tz7uJ +uGDWmWwVQHVlxYYt3JYBEUI8dE8Iw8BqnO6kuVQ+Y9rtleNCdeJl8C9W1vkSASvA +IT+va8ojoJbpyU66DslU57rCPOU7BjYn3+Hd0xTJfGGk8Pv1ThHaNma3bilFZ0pR +Bup5pIc95uOXoAAcYjz95asGWUOTOewIAPEBPw3pB5eoVn70wTkHjF8CPs8FVCmO +Qq3OOH7lyf4d1V8ZPT0bsyNfNo32ODh1afpOsRaclzJBtiwqsKfyE8H65rt6zpJ6 +p2DXdpkvUdRO5/ZQF5Hq/VxdxYamqQQlyFbuhzSLYsPN1q3LWi8HobUeqSkaz4pV +O/CQRZLP6BvYZvex7v3BoKUYkVAeWTGU6WCOPaGp1OxdkQYdryUg/A== +=Xet7 +-----END PGP PUBLIC KEY BLOCK----- +"; + +#[derive(Debug, Error)] +pub enum LicenseError { + #[error("Provided license is invalid: {0}")] + InvalidLicense(String), + #[error("Provided signature does not match the license")] + SignatureMismatch, + #[error("Provided signature is invalid")] + InvalidSignature, + #[error("Database error")] + DbError(#[from] SqlxError), + #[error("License decoding error: {0}")] + DecodeError(String), + #[error("License is expired and has reached its maximum overdue time, please contact salesdefguard.net")] + LicenseExpired, + #[error("License not found")] + LicenseNotFound, + #[error("License server error: {0}")] + LicenseServerError(String), +} + +#[derive(Debug, Serialize, Deserialize)] +struct RefreshRequestResponse { + key: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct License { + pub customer_id: String, + pub subscription: bool, + pub valid_until: Option>, +} + +impl License { + #[must_use] + pub fn new( + customer_id: String, + subscription: bool, + valid_until: Option>, + ) -> Self { + Self { + customer_id, + subscription, + valid_until, + } + } + + fn decode(bytes: &[u8]) -> Result, LicenseError> { + let bytes = BASE64_STANDARD.decode(bytes).map_err(|_| { + LicenseError::DecodeError( + "Failed to decode the license key, check if the provided key is correct." + .to_string(), + ) + })?; + Ok(bytes) + } + + fn verify_signature(data: &[u8], signature: &[u8]) -> Result<(), LicenseError> { + let sig = StandaloneSignature::from_bytes(signature) + .map_err(|_| LicenseError::InvalidSignature)?; + let (public_key, _headers_public) = + SignedPublicKey::from_string(PUBLIC_KEY).expect("Failed to parse the public key"); + + // If the public key has subkeys, extract the signing key from them + // Otherwise, use the primary key + if public_key.public_subkeys.is_empty() { + debug!( + "Using the public key's primary key {:?} to verify the signature...", + public_key.key_id() + ); + sig.verify(&public_key, data) + .map_err(|_| LicenseError::SignatureMismatch) + } else { + let signing_key = public_key + .public_subkeys + .into_iter() + .find(KeyTrait::is_signing_key) + .ok_or(LicenseError::LicenseServerError( + "Failed to find a signing key in the provided public key".to_string(), + ))?; + debug!( + "Using the public key's subkey {:?} to verify the signature...", + signing_key.key_id() + ); + sig.verify(&signing_key, data) + .map_err(|_| LicenseError::SignatureMismatch) + } + } + + /// Deserialize the license object from a base64 encoded string. + /// Also verifies the signature of the license + pub fn from_base64(key: &str) -> Result { + debug!("Decoding the license key from a provided base64 string..."); + let bytes = key.as_bytes(); + let decoded = Self::decode(bytes)?; + let slice: &[u8] = &decoded; + debug!("Decoded the license key, deserializing the license object..."); + + let license_key = LicenseKey::decode(slice).map_err(|_| { + LicenseError::DecodeError( + "The license key is malformed, check if the provided key is correct.".to_string(), + ) + })?; + let metadata_bytes: &[u8] = &license_key.metadata; + let signature_bytes: &[u8] = &license_key.signature; + debug!("Deserialized the license object, verifying the license signature..."); + + match Self::verify_signature(metadata_bytes, signature_bytes) { + Ok(()) => { + info!("Successfully decoded the license and validated the license signature"); + let metadata = LicenseMetadata::decode(metadata_bytes).map_err(|_| { + LicenseError::DecodeError("Failed to decode the license metadata".to_string()) + })?; + + let valid_until = match metadata.valid_until { + Some(until) => DateTime::from_timestamp(until, 0), + None => None, + }; + + let license = + License::new(metadata.customer_id, metadata.subscription, valid_until); + + if license.requires_renewal() { + if license.is_max_overdue() { + warn!("The provided license has expired and reached its maximum overdue time, please contact salesdefguard.net"); + } else { + warn!("The provided license is about to expire and requires a renewal. An automatic renewal process will attempt to renew the license soon. Alternatively, automatic renewal attempt will be also performed at the next defguard start."); + } + } + + if !license.subscription && license.is_expired() { + warn!("The provided license is not a subscription and has expired, please contact salesdefguard.net"); + } + + Ok(license) + } + Err(_) => Err(LicenseError::SignatureMismatch), + } + } + + /// Get the key from the database + async fn get_key(pool: &PgPool) -> Result, LicenseError> { + let settings = Settings::get_settings(pool).await?; + match settings.license { + Some(key) => { + if key.is_empty() { + Ok(None) + } else { + Ok(Some(key)) + } + } + None => Ok(None), + } + } + + /// Create the license object based on the license key stored in the database. + /// Automatically decodes and deserializes the keys and verifies the signature. + pub async fn load(pool: &PgPool) -> Result, LicenseError> { + if let Some(key) = Self::get_key(pool).await? { + Ok(Some(Self::from_base64(&key)?)) + } else { + debug!("No license key found in the database"); + Ok(None) + } + } + + /// Try to load the license from the database, if the license requires a renewal, try to renew it. + /// If the renewal fails, it will return the old license for the renewal service to renew it later. + pub async fn load_or_renew(pool: &PgPool) -> Result, LicenseError> { + match Self::load(pool).await? { + Some(license) => { + if license.requires_renewal() { + if license.is_max_overdue() { + Err(LicenseError::LicenseExpired) + } else { + info!("License requires renewal, trying to renew it..."); + match renew_license(pool).await { + Ok(new_key) => { + let new_license = License::from_base64(&new_key)?; + save_license_key(pool, &new_key).await?; + info!("Successfully renewed and loaded the license, new license key saved to the database"); + Ok(Some(new_license)) + } + Err(err) => { + error!("Failed to renew the license: {err}"); + Ok(Some(license)) + } + } + } + } else { + info!("Successfully loaded the license from the database."); + Ok(Some(license)) + } + } + None => Ok(None), + } + } + + /// Checks whether the license is past its expiry date (`valid_until` field) + /// + /// NOTE: license should be considered valid for an additional period of `MAX_OVERDUE_TIME`. + /// If you want to check if the license reached this point, use `is_max_overdue` instead. + #[must_use] + pub fn is_expired(&self) -> bool { + match self.valid_until { + Some(time) => time < Utc::now(), + None => false, + } + } + + /// Checks how much time has left until the `valid_until` time. + #[must_use] + pub fn time_left(&self) -> Option { + self.valid_until.map(|time| time - Utc::now()) + } + + /// Gets the time the license is past its expiry date. + /// If the license doesn't have a `valid_until` field, it will return 0. + #[must_use] + pub fn time_overdue(&self) -> TimeDelta { + match self.valid_until { + Some(time) => { + let delta = Utc::now() - time; + if delta <= TimeDelta::zero() { + TimeDelta::zero() + } else { + delta + } + } + None => TimeDelta::zero(), + } + } + + /// Checks whether we should try to renew the license. + #[must_use] + pub fn requires_renewal(&self) -> bool { + if self.subscription { + if let Some(remaining) = self.time_left() { + remaining <= RENEWAL_TIME + } else { + false + } + } else { + false + } + } + + /// Checks if the license has reached its maximum overdue time. + #[must_use] + pub fn is_max_overdue(&self) -> bool { + if !self.subscription { + // Non-subscription licenses are considered expired immediately, no grace period is required + self.is_expired() + } else { + self.time_overdue() > MAX_OVERDUE_TIME + } + } +} + +/// Exchange the currently stored key for a new one from the license server. +/// +/// Doesn't update the cached license, nor does it save the new key in the database. +async fn renew_license(db_pool: &PgPool) -> Result { + debug!("Exchanging license for a new one..."); + let Some(old_license_key) = Settings::get_settings(db_pool).await?.license else { + return Err(LicenseError::LicenseNotFound); + }; + + let client = reqwest::Client::new(); + + let request_body = RefreshRequestResponse { + key: old_license_key, + }; + + let new_license_key = match client + .post(LICENSE_SERVER_URL) + .json(&request_body) + .header(reqwest::header::USER_AGENT, format!("DefGuard/{VERSION}")) + .timeout(Duration::from_secs(10)) + .send() + .await + { + Ok(response) => match response.status() { + reqwest::StatusCode::OK => { + let response: RefreshRequestResponse = response.json().await.map_err(|err| { + error!("Failed to parse the response from the license server while trying to renew the license: {err:?}"); + LicenseError::LicenseServerError(err.to_string()) + })?; + response.key + } + status => { + let status_message = response.text().await.unwrap_or_default(); + let message = format!( + "Failed to renew the license, the license server returned a status code {status} with error: {status_message}" + ); + return Err(LicenseError::LicenseServerError(message)); + } + }, + Err(err) => { + return Err(LicenseError::LicenseServerError(err.to_string())); + } + }; + + info!("Successfully exchanged the license for a new one"); + + Ok(new_license_key) +} + +/// Helper function used to check if the cached license should be considered valid. +/// As the license is often passed around in the form of `Option`, this function takes care +/// of the whole logic related to checking whether the license is even present in the first place. +/// +/// This function checks the following two things: +/// 1. Does the cached license exist +/// 2. Is the cached license past its maximum expiry date +pub fn validate_license(license: Option<&License>) -> Result<(), LicenseError> { + debug!("Validating if the license is present and not expired..."); + match license { + Some(license) => { + if license.is_max_overdue() { + return Err(LicenseError::LicenseExpired); + } + Ok(()) + } + None => Err(LicenseError::LicenseNotFound), + } +} + +/// Helper function to save the license key string in the database +async fn save_license_key(pool: &PgPool, key: &str) -> Result<(), LicenseError> { + debug!("Saving the license key to the database..."); + let mut settings = Settings::get_settings(pool).await?; + settings.license = Some(key.to_string()); + settings.save_license(pool).await?; + + info!("Successfully saved license key to the database."); + + Ok(()) +} + +/// Helper function to update the cached license mutex. The mutex is used mainly in the appstate. +pub fn update_cached_license(key: Option<&str>) -> Result<(), LicenseError> { + debug!("Updating the cached license information with the provided key..."); + let license = if let Some(key) = key { + // Handle the Some("") case + if key.is_empty() { + debug!("The new license key is empty, clearing the cached license"); + None + } else { + debug!("A new license key has been provided, decoding and validating it..."); + Some(License::from_base64(key)?) + } + } else { + None + }; + set_cached_license(license); + + info!("Successfully updated the cached license information."); + + Ok(()) +} + +/// Amount of time before the license expiry date we should start the renewal attempts. +const RENEWAL_TIME: TimeDelta = TimeDelta::hours(24); + +/// Maximum amount of time a license can be over its expiry date. +const MAX_OVERDUE_TIME: TimeDelta = TimeDelta::days(14); + +/// Periodic license check task +const CHECK_PERIOD: Duration = Duration::from_secs(12 * 60 * 60); + +/// Periodic license check task for the case when no license is present +const CHECK_PERIOD_NO_LICENSE: Duration = Duration::from_secs(24 * 60 * 60); + +/// Periodic license check task for the case when the license is about to expire +const CHECK_PERIOD_RENEWAL_WINDOW: Duration = Duration::from_secs(60 * 60); + +pub async fn run_periodic_license_check(pool: PgPool) -> Result<(), LicenseError> { + let mut check_period: Duration = CHECK_PERIOD; + info!( + "Starting periodic license renewal check every {}", + format_duration(check_period) + ); + loop { + debug!("Checking the license status..."); + // Check if the license is present in the mutex, if not skip the check + if get_cached_license().is_none() { + debug!("No license found, skipping license check"); + sleep(CHECK_PERIOD_NO_LICENSE).await; + continue; + } + + // Check if the license requires renewal, uses the cached value to be more efficient + // The block here is to avoid holding the lock through awaits + // + // Multiple locks here may cause a race condition if the user decides to update the license key + // while the renewal is in progress. However this seems like a rare case and shouldn't be very problematic. + let requires_renewal = { + let license = get_cached_license(); + debug!("Checking if the license {license:?} requires a renewal..."); + + match &*license { + Some(license) => { + if license.requires_renewal() { + // check if we are pass the maximum expiration date, after which we don't + // want to try to renew the license anymore + if license.is_max_overdue() { + check_period = CHECK_PERIOD; + warn!("Your license has expired and reached its maximum overdue date, please contact sales at salesdefguard.net"); + debug!("Changing check period to {}", format_duration(check_period)); + false + } else { + debug!("License requires renewal, as it is about to expire and is not past the maximum overdue time"); + true + } + } else { + // This if is only for logging purposes, to provide more detailed information + if license.subscription { + debug!( + "License doesn't need to be renewed yet, skipping renewal check" + ); + } else { + debug!("License is not a subscription, skipping renewal check"); + } + false + } + } + None => { + debug!("No license found, skipping license check"); + false + } + } + }; + + if requires_renewal { + info!("License requires renewal, renewing license..."); + check_period = CHECK_PERIOD_RENEWAL_WINDOW; + debug!("Changing check period to {}", format_duration(check_period)); + match renew_license(&pool).await { + Ok(new_license_key) => match save_license_key(&pool, &new_license_key).await { + Ok(()) => { + update_cached_license(Some(&new_license_key))?; + check_period = CHECK_PERIOD; + debug!("Changing check period to {}", format_duration(check_period)); + info!("Successfully renewed the license"); + } + Err(err) => { + error!("Couldn't save the newly fetched license key to the database, error: {}", err); + } + }, + Err(err) => { + warn!( + "Failed to renew the license: {err}. Retrying in {}", + format_duration(check_period) + ); + } + } + } + + sleep(check_period).await; + } +} + +// Mock public key +#[cfg(test)] +pub(crate) const PUBLIC_KEY: &str = "-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBGa0jtoBCAC63WkY0btyVzHI8JGVfIkFClNggcDgK+X/if5ndJtHKRXcW6DB +bRTBNCdUr7sDzCMEYWu8t400Yn/mrLKuubA3G6rp3Eo2nHnOicoZ6mfAdUQL862l +m9M8zpJtFodWR5G0nznyvabQi9kI1JT87DEIAdfLhN4eoMpgEm+jASSgFeT63oJ9 +fLHofMZLwYZW/mqsnGxElmUsfnVWeseUSgmKBP4IgdtX4LsCx8XiOyQJww6bEUTj +ZBSqwwuRa1ybtsV3ihEKjDBmXQo5+J3fsadm/6m5PRJVk5rq9/LGVKIBG9m/x6Pn +xeYaLsjNyAwOSHH2KpeBLPVEfjsqWRt8fyAzABEBAAG0HEF1dG9nZW5lcmF0ZWQg +S2V5IDxkZWZndWFyZD6JAU4EEwEKADgWIQTyH9Rb8S5I78bRYzghGgZ+AdnRKwUC +ZrSO2gIbLwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAhGgZ+AdnRKyzzCACW +oGBnAPHkCuvlnZjcYUAJVrjI/S02x4t3wFjaFOu+GQSjeB+AjDawF/S4D5ReQ8iq +D3dTvno3lk/F5HvqV/ZDU9WMmkDFzJoEwKbNIlWwQvvrTnoyy7lpKskNxwwsErEL +2+rW+lW/N5KNHFaUh2d5JhK08VRPfyl0WA8gqQ99Wnhq4rHF7ijKFm3im0RlzkMI +NTXxxee/9J0/Pzh+7zFZlMxnnjwiHlxJXpQFwh7+TS9C3IpChW3ipyPgp1DkzsNv +Xry1crUOhOyEozdKYh2H6tZEi3bjtGwpYkXJs/g3f6HPKjS8rDOMXw4Japb7LYtC +Aao60J8cOm8J96u1MsUK +=6cHp +-----END PGP PUBLIC KEY BLOCK----- +"; + +#[cfg(test)] +mod test { + use chrono::TimeZone; + + use super::*; + + #[test] + fn test_license() { + let license = "CigKIDVhMGRhZDRiOWNmZTRiNzZiYjkzYmI1Y2Q5MGM2ZjdjGLL+lrYGErYCiQEzBAABCgAdFiEE8h/UW/EuSO/G0WM4IRoGfgHZ0SsFAmbFvzUACgkQIRoGfgHZ0SuNQggAioLovxAyrgAn+LPO42QIlVHYG8oTs3jnpM0BMx3cXbfy7M0ECsC10HpzIkundems7SgYO/+iJfMMe4mj3kiA+uwacCmPW6VWTIVEIpX2jqRpv7DcDnUSeAszySZl6KhQS+35IPC0Gs2yQNU4/mDsa4VUv9DiL8s7rMM89fe4QmtjVRpFQVgGLm4IM+mRIXTySB2RwmVzw8+YE4z+w4emLxaKWjw4Q7CQxykkPNGlBj224jozs/Biw9eDYCbJOT/5KXNqZ2peht59n6RMVc0SNKE26E8hDmJ61M0Tzj57wQ6nZ3yh6KGyTdCIc9Y9wcrHwZ1Yw1tdh8j/fULUyPtNyA=="; + let license = License::from_base64(license).unwrap(); + assert_eq!(license.customer_id, "5a0dad4b9cfe4b76bb93bb5cd90c6f7c"); + assert!(!license.subscription); + assert_eq!( + license.valid_until.unwrap(), + Utc.with_ymd_and_hms(2024, 8, 21, 10, 19, 30).unwrap() + ); + + assert!(license.is_expired()); + } + + #[test] + fn test_new_license() { + // This key has an additional test_field in the metadata that doesn't exist in the proto definition + // It should still be able to decode the license correctly + let license = "CjIKIDVhMGRhZDRiOWNmZTRiNzZiYjkzYmI1Y2Q5MGM2ZjdjGMv0lrYGIggxMjM0NTY3OBK2AokBMwQAAQoAHRYhBPIf1FvxLkjvxtFjOCEaBn4B2dErBQJmxbpSAAoJECEaBn4B2dEru6sH/0FBWgj8Nl1n/hwx1CdwrmKkKOCRpTf244wS07EcwQDr/A5TA011Y4PFJBSFfoIlyuGFHh20KoczFVUPfyiIGkqMMGOe8BH0Pbst6n5hd1S67m5fKgNV+NdaWg1aJfMdbGdworpZWTnsHnsTnER+fhoC/CohPtTshTdBZX0wmyfAWKQW3HM0YcE73+KFvGMzTMyin/bOrjr7bW0d5yoQLaEIpAASTlb6DaX5avyTFitXLf77cMjRu4wysnlPfwIpSqQI+ESHNh+OepOUqxmox+U9hGVtvlIJhvBOLgJ/Kmldc1Kj7uZaldLhWDG5e7+dVdnhbwfuoUsgS9jmpAmeWsg="; + let license = License::from_base64(license).unwrap(); + + assert_eq!(license.customer_id, "5a0dad4b9cfe4b76bb93bb5cd90c6f7c"); + assert!(!license.subscription); + assert_eq!( + license.valid_until.unwrap(), + Utc.with_ymd_and_hms(2024, 8, 21, 9, 58, 35).unwrap() + ); + } + + #[test] + fn test_invalid_license() { + let license = "CigKIDVhMGRhZDRiOWNmZTRiNzZiYjkzYmI1Y2Q5MGM2ZjdjGLL+lrYGErYCiQEzBAABCgAdFiEE8h/UW/EuSO/G0WM4IRoGfgHZ0SsFAmbFvzUACgkQIRoGfgHZ0SuNQggAioLovxAyrgAn+LPO42QIlVHYG8oTs3jnpM0BMx3cXbfy7M0ECsC10HpzIkundems7SgYO/+iJfMMe4mj3kiA+uwacCmPW6VWTIVEIpX2jqRpv7DcDnUSeAszySZl6KhQS+35IPC0Gs2yQNU4/mDsa4VUv9DiL8s7rMM89fe4QmtjVRpFQVgGLm4IM+mRIXTySB2RwmVzw8+YE4z+w4emLxaKWjw4Q7CQxykkPNGlBj224jozs/Biw9eDYCbJOT/5KXNqZ2peht59n6RMVc0SNKE26E8hDmJ61M0Tzj57wQ6nZ3yh6KGyTdCIc9Y9wcrHwZ1Yw1tdh8j/fULUyPtNyA=="; + let license = License::from_base64(license).unwrap(); + assert!(validate_license(Some(&license)).is_err()); + assert!(validate_license(None).is_err()); + + // One day past the expiry date, non-subscription license + let license = License { + customer_id: "test".to_string(), + subscription: false, + valid_until: Some(Utc::now() - TimeDelta::days(1)), + }; + assert!(validate_license(Some(&license)).is_err()); + + // One day before the expiry date, non-subscription license + let license = License { + customer_id: "test".to_string(), + subscription: false, + valid_until: Some(Utc::now() + TimeDelta::days(1)), + }; + assert!(validate_license(Some(&license)).is_ok()); + + // No expiry date, non-subscription license + let license = License { + customer_id: "test".to_string(), + subscription: false, + valid_until: None, + }; + assert!(validate_license(Some(&license)).is_ok()); + + // One day past the maximum overdue date + let license = License { + customer_id: "test".to_string(), + subscription: true, + valid_until: Some(Utc::now() - MAX_OVERDUE_TIME - TimeDelta::days(1)), + }; + assert!(validate_license(Some(&license)).is_err()); + + // One day before the maximum overdue date + let license = License { + customer_id: "test".to_string(), + subscription: true, + valid_until: Some(Utc::now() - MAX_OVERDUE_TIME + TimeDelta::days(1)), + }; + assert!(validate_license(Some(&license)).is_ok()); + } +} diff --git a/src/enterprise/mod.rs b/src/enterprise/mod.rs new file mode 100644 index 0000000000..dadca5b4ce --- /dev/null +++ b/src/enterprise/mod.rs @@ -0,0 +1,4 @@ +pub mod db; +pub mod grpc; +pub mod handlers; +pub mod license; diff --git a/src/enterprise/proto/license.proto b/src/enterprise/proto/license.proto new file mode 100644 index 0000000000..b9fa7b29dc --- /dev/null +++ b/src/enterprise/proto/license.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; +package license; + +message LicenseMetadata { + string customer_id = 1; + bool subscription = 2; + optional int64 valid_until = 3; +} + +message LicenseKey { + bytes metadata = 1; + bytes signature = 2; +} diff --git a/src/error.rs b/src/error.rs index cf60ea6a93..bc13ad5da0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ use axum::http::StatusCode; use sqlx::error::Error as SqlxError; use thiserror::Error; +use utoipa::ToSchema; use crate::{ auth::failed_login::FailedLoginError, @@ -8,13 +9,14 @@ use crate::{ device::DeviceError, enrollment::TokenError, error::ModelError, wireguard::WireguardNetworkError, }, + enterprise::license::LicenseError, grpc::GatewayMapError, ldap::error::LdapError, templates::TemplateError, }; /// Represents kinds of error that occurred -#[derive(Debug, Error)] +#[derive(Debug, Error, ToSchema)] pub enum WebError { #[error("GRPC error: {0}")] Grpc(String), @@ -52,6 +54,8 @@ pub enum WebError { TemplateError(#[from] TemplateError), #[error("Server config missing")] ServerConfigMissing, + #[error("License error: {0}")] + LicenseError(#[from] LicenseError), } impl From for WebError { @@ -71,7 +75,8 @@ impl From for WebError { match error { LdapError::ObjectNotFound(msg) => Self::ObjectNotFound(msg), LdapError::Ldap(msg) => Self::Ldap(msg), - LdapError::MissingSettings => Self::Ldap("LDAP settings are missing".to_string()), + LdapError::MissingSettings => Self::Ldap("LDAP settings are missing".into()), + LdapError::Database => Self::Ldap("Database problem".into()), } } } diff --git a/src/grpc/auth.rs b/src/grpc/auth.rs index afa8a9e118..fced88ff4d 100644 --- a/src/grpc/auth.rs +++ b/src/grpc/auth.rs @@ -1,6 +1,7 @@ use std::sync::{Arc, Mutex}; use jsonwebtoken::errors::Error as JWTError; +use sqlx::PgPool; use tonic::{Request, Response, Status}; use crate::{ @@ -8,20 +9,20 @@ use crate::{ failed_login::{check_username, log_failed_login_attempt, FailedLoginMap}, Claims, ClaimsType, }, - db::{DbPool, User}, + db::User, server_config, }; tonic::include_proto!("auth"); pub struct AuthServer { - pool: DbPool, + pool: PgPool, failed_logins: Arc>, } impl AuthServer { #[must_use] - pub fn new(pool: DbPool, failed_logins: Arc>) -> Self { + pub fn new(pool: PgPool, failed_logins: Arc>) -> Self { Self { pool, failed_logins, diff --git a/src/grpc/desktop_client_mfa.rs b/src/grpc/desktop_client_mfa.rs index 107ff5d31a..0dfcb2a567 100644 --- a/src/grpc/desktop_client_mfa.rs +++ b/src/grpc/desktop_client_mfa.rs @@ -1,3 +1,10 @@ +use std::collections::HashMap; + +use chrono::Utc; +use sqlx::PgPool; +use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; +use tonic::Status; + use super::proto::{ ClientMfaFinishRequest, ClientMfaFinishResponse, ClientMfaStartRequest, ClientMfaStartResponse, MfaMethod, @@ -6,27 +13,23 @@ use crate::{ auth::{Claims, ClaimsType}, db::{ models::device::{DeviceInfo, DeviceNetworkInfo, WireguardNetworkDevice}, - DbPool, Device, GatewayEvent, User, UserInfo, WireguardNetwork, + Device, GatewayEvent, Id, User, UserInfo, WireguardNetwork, }, handlers::mail::send_email_mfa_code_email, mail::Mail, }; -use chrono::Utc; -use std::collections::HashMap; -use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; -use tonic::Status; const CLIENT_SESSION_TIMEOUT: u64 = 60 * 5; // 10 minutes struct ClientLoginSession { method: MfaMethod, - location: WireguardNetwork, - device: Device, - user: User, + location: WireguardNetwork, + device: Device, + user: User, } pub(super) struct ClientMfaServer { - pool: DbPool, + pool: PgPool, mail_tx: UnboundedSender, wireguard_tx: Sender, sessions: HashMap, @@ -35,7 +38,7 @@ pub(super) struct ClientMfaServer { impl ClientMfaServer { #[must_use] pub fn new( - pool: DbPool, + pool: PgPool, mail_tx: UnboundedSender, wireguard_tx: Sender, ) -> Self { @@ -202,13 +205,13 @@ impl ClientMfaServer { // validate code match method { MfaMethod::Totp => { - if !user.verify_totp_code(request.code) { + if !user.verify_totp_code(&request.code.to_string()) { error!("Provided TOTP code is not valid"); return Err(Status::unauthenticated("unauthorized")); } } MfaMethod::Email => { - if !user.verify_email_mfa_code(request.code) { + if !user.verify_email_mfa_code(&request.code.to_string()) { error!("Provided email code is not valid"); return Err(Status::unauthenticated("unauthorized")); } @@ -222,12 +225,8 @@ impl ClientMfaServer { })?; // fetch device config for the location - let Ok(Some(mut network_device)) = WireguardNetworkDevice::find( - &mut *transaction, - device.id.expect("Missing device ID"), - location.id.expect("Missing location ID"), - ) - .await + let Ok(Some(mut network_device)) = + WireguardNetworkDevice::find(&mut *transaction, device.id, location.id).await else { error!("Failed to fetch network config for device {device} and location {location}"); return Err(Status::internal("unexpected error")); @@ -255,7 +254,7 @@ impl ClientMfaServer { let device_info = DeviceInfo { device: device.clone(), network_info: vec![DeviceNetworkInfo { - network_id: location.id.expect("Missing location ID"), + network_id: location.id, device_wireguard_ip: network_device.wireguard_ip, preshared_key: network_device.preshared_key, is_authorized: network_device.is_authorized, diff --git a/src/grpc/enrollment.rs b/src/grpc/enrollment.rs index 48dc0d44a8..c308d75238 100644 --- a/src/grpc/enrollment.rs +++ b/src/grpc/enrollment.rs @@ -1,14 +1,30 @@ use std::sync::Arc; +use ipnetwork::IpNetwork; +use sqlx::{PgPool, Transaction}; +use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; +use tonic::Status; +use uaparser::UserAgentParser; + +use super::{ + proto::{ + ActivateUserRequest, AdminInfo, Device as ProtoDevice, DeviceConfig as ProtoDeviceConfig, + DeviceConfigResponse, EnrollmentStartRequest, EnrollmentStartResponse, ExistingDevice, + InitialUserInfo, NewDevice, + }, + InstanceInfo, +}; use crate::{ db::{ models::{ - device::{DeviceConfig, DeviceInfo, WireguardNetworkDevice}, + device::{DeviceConfig, DeviceInfo}, enrollment::{Token, TokenError, ENROLLMENT_TOKEN_TYPE}, - wireguard::WireguardNetwork, + polling_token::PollingToken, }, - DbPool, Device, GatewayEvent, Settings, User, + Device, GatewayEvent, Id, Settings, User, }, + enterprise::db::models::enterprise_settings::EnterpriseSettings, + grpc::utils::build_device_config_response, handlers::{mail::send_new_device_added_email, user::check_password_strength}, headers::get_device_info, ldap::utils::ldap_add_user, @@ -16,64 +32,19 @@ use crate::{ server_config, templates::{self, TemplateLocation}, }; -use ipnetwork::IpNetwork; -use reqwest::Url; -use sqlx::Transaction; -use tokio::sync::{broadcast::Sender, mpsc::UnboundedSender}; -use tonic::Status; -use uaparser::UserAgentParser; - -use super::proto::{ - ActivateUserRequest, AdminInfo, Device as ProtoDevice, DeviceConfig as ProtoDeviceConfig, - DeviceConfigResponse, EnrollmentStartRequest, EnrollmentStartResponse, ExistingDevice, - InitialUserInfo, NewDevice, -}; pub(super) struct EnrollmentServer { - pool: DbPool, + pool: PgPool, wireguard_tx: Sender, mail_tx: UnboundedSender, user_agent_parser: Arc, ldap_feature_active: bool, } -struct InstanceInfo { - id: uuid::Uuid, - name: String, - url: Url, - proxy_url: Url, - username: String, -} - -impl InstanceInfo { - pub fn new>(settings: Settings, username: S) -> Self { - let config = server_config(); - InstanceInfo { - id: settings.uuid, - name: settings.instance_name, - url: config.url.clone(), - proxy_url: config.enrollment_url.clone(), - username: username.into(), - } - } -} - -impl From for super::proto::InstanceInfo { - fn from(instance: InstanceInfo) -> Self { - Self { - name: instance.name, - id: instance.id.to_string(), - url: instance.url.to_string(), - proxy_url: instance.proxy_url.to_string(), - username: instance.username, - } - } -} - impl EnrollmentServer { #[must_use] pub fn new( - pool: DbPool, + pool: PgPool, wireguard_tx: Sender, mail_tx: UnboundedSender, user_agent_parser: Arc, @@ -89,21 +60,32 @@ impl EnrollmentServer { } } - // check if token provided with request corresponds to a valid enrollment session - async fn validate_session(&self, token: Option<&str>) -> Result { + /// Checks if token provided with request corresponds to a valid enrollment session + async fn validate_session(&self, token: &Option) -> Result { + info!("Validating enrollment session. Token: {token:?}"); let Some(token) = token else { error!("Missing authorization header in request"); return Err(Status::unauthenticated("Missing authorization header")); }; - debug!("Validating enrollment session token: {token}"); - let enrollment = Token::find_by_id(&self.pool, token).await?; + debug!("Found matching token, verifying validity: {enrollment:?}."); + if !enrollment + .token_type + .as_ref() + .is_some_and(|token_type| token_type == ENROLLMENT_TOKEN_TYPE) + { + error!( + "Invalid token type used in enrollment process: {:?}", + enrollment.token_type + ); + return Err(Status::permission_denied("invalid token")); + } if enrollment.is_session_valid(server_config().enrollment_session_timeout.as_secs()) { - info!("Enrollment session validated"); + info!("Enrollment session validated: {enrollment:?}"); Ok(enrollment) } else { - error!("Enrollment session expired"); - Err(Status::unauthenticated("Enrollment session expired")) + error!("Enrollment session expired: {enrollment:?}"); + Err(Status::unauthenticated("Session expired")) } } @@ -120,6 +102,7 @@ impl EnrollmentServer { ) -> Result { debug!("Starting enrollment session, request: {request:?}"); // fetch enrollment token + debug!("Try to find an enrollment token {}.", request.token); let mut enrollment = Token::find_by_id(&self.pool, &request.token).await?; if let Some(token_type) = &enrollment.token_type { @@ -132,45 +115,111 @@ impl EnrollmentServer { let user = enrollment.fetch_user(&self.pool).await?; let admin = enrollment.fetch_admin(&self.pool).await?; + debug!( + "Checking if user {}({:?}) is active", + user.username, user.id + ); if !user.is_active { - warn!("Can't start enrollment for disabled user {}", user.username); + warn!( + "Can't start enrollment for disabled user {}.", + user.username + ); return Err(Status::permission_denied("user is disabled")); }; + info!( + "User {}({:?}) is active, proceeding with enrollment", + user.username, user.id + ); - let mut transaction = self.pool.begin().await.map_err(|_| { - error!("Failed to begin transaction"); + let mut transaction = self.pool.begin().await.map_err(|err| { + error!("Failed to begin a transaction for enrollment: {err}"); Status::internal("unexpected error") })?; // validate token & start session - debug!("Starting enrollment session for user {}", user.username); + debug!( + "Validating enrollment token and starting session for user {}({:?})", + user.username, user.id, + ); let session_deadline = enrollment .start_session( &mut transaction, server_config().enrollment_session_timeout.as_secs(), ) .await?; - info!("Enrollment session started for user {}", user.username); + info!( + "Enrollment session started for user {}({:?})", + user.username, user.id + ); + debug!( + "Retrieving settings for enrollment of user {}({:?}).", + user.username, user.id + ); let settings = Settings::get_settings(&mut *transaction) .await - .map_err(|_| { - error!("Failed to get settings"); + .map_err(|err| { + error!("Failed to get settings: {err}"); Status::internal("unexpected error") })?; + debug!("Settings: {settings:?}"); + + debug!( + "Retrieving enterprise settings for enrollment of user {}({:?}).", + user.username, user.id + ); + let enterprise_settings = + EnterpriseSettings::get(&mut *transaction) + .await + .map_err(|err| { + error!("Failed to get enterprise settings: {err}"); + Status::internal("unexpected error") + })?; + debug!("Enterprise settings: {enterprise_settings:?}"); let vpn_setup_optional = settings.enrollment_vpn_step_optional; - let instance_info = InstanceInfo::new(settings, &user.username); + debug!( + "Retrieving instance info for user {}({:?}).", + user.username, user.id + ); + let instance_info = InstanceInfo::new(settings, &user.username, &enterprise_settings); + debug!("Instance info {instance_info:?}"); + debug!( + "Preparing initial user info to send for user enrollment, user {}({:?}).", + user.username, user.id + ); + let (username, user_id) = (user.username.clone(), user.id); let user_info = InitialUserInfo::from_user(&self.pool, user) .await - .map_err(|_| { - error!("Failed to get user info"); + .map_err(|err| { + error!( + "Failed to get user info for user {}({:?}): {err}", + username, user_id, + ); Status::internal("unexpected error") })?; + debug!("User info {user_info:?}"); + debug!("Trying to get basic admin info..."); let admin_info = admin.map(AdminInfo::from); + debug!("Admin info {admin_info:?}"); + debug!( + "Creating enrollment start response for user {}({:?}).", + username, user_id, + ); + let enterprise_settings = + EnterpriseSettings::get(&mut *transaction) + .await + .map_err(|err| { + error!("Failed to get enterprise settings: {err}"); + Status::internal("unexpected error") + })?; + let enrollment_settings = super::proto::Settings { + vpn_setup_optional, + only_client_activation: enterprise_settings.only_client_activation, + }; let response = super::proto::EnrollmentStartResponse { admin: admin_info, user: Some(user_info), @@ -178,17 +227,19 @@ impl EnrollmentServer { final_page_content: enrollment .get_welcome_page_content(&mut transaction) .await?, - vpn_setup_optional, instance: Some(instance_info.into()), + settings: Some(enrollment_settings), }; + debug!("Response {response:?}"); - transaction.commit().await.map_err(|_| { - error!("Failed to commit transaction"); + transaction.commit().await.map_err(|err| { + error!("Failed to commit transaction: {err}"); Status::internal("unexpected error") })?; Ok(response) } else { + debug!("Invalid enrollment token, the token does not have specified type."); Err(Status::permission_denied("invalid token")) } } @@ -199,7 +250,7 @@ impl EnrollmentServer { req_device_info: Option, ) -> Result<(), Status> { debug!("Activating user account: {request:?}"); - let enrollment = self.validate_session(request.token.as_deref()).await?; + let enrollment = self.validate_session(&request.token).await?; let ip_address; let device_info; @@ -211,20 +262,29 @@ impl EnrollmentServer { ip_address = String::new(); device_info = None; } + debug!("Ip address {}, device info {device_info:?}", ip_address); // check if password is strong enough + debug!("Verifying password strength for user activation process."); if let Err(err) = check_password_strength(&request.password) { error!("Password not strong enough: {err}"); return Err(Status::invalid_argument("password not strong enough")); } + debug!("Password is strong enough to complete the user activation process."); // fetch related users let mut user = enrollment.fetch_user(&self.pool).await?; + debug!( + "Fetching user {} data to check if the user already has a password.", + user.username + ); if user.has_password() { error!("User {} already activated", user.username); return Err(Status::invalid_argument("user already activated")); } + debug!("User doesn't have a password yet. Continue user activation process..."); + debug!("Verify if the user is active or disabled."); if !user.is_active { warn!( "Can't finalize enrollment for disabled user {}", @@ -232,33 +292,41 @@ impl EnrollmentServer { ); return Err(Status::invalid_argument("user is disabled")); } + debug!("User is active."); - let mut transaction = self.pool.begin().await.map_err(|_| { - error!("Failed to begin transaction"); + let mut transaction = self.pool.begin().await.map_err(|err| { + error!("Failed to begin transaction: {err}"); Status::internal("unexpected error") })?; // update user + info!("Update user details and set a new password."); user.phone = request.phone_number; user.set_password(&request.password); user.save(&mut *transaction).await.map_err(|err| { error!("Failed to update user {}: {err}", user.username); Status::internal("unexpected error") })?; + debug!("Updating user details ended with success."); // sync with LDAP + debug!("Add user to ldap: {}.", self.ldap_feature_active); if self.ldap_feature_active { + debug!("Syncing with LDAP."); let _result = ldap_add_user(&self.pool, &user, &request.password).await; }; + debug!("Retriving settings to send welcome email..."); let settings = Settings::get_settings(&mut *transaction) .await - .map_err(|_| { - error!("Failed to get settings"); + .map_err(|err| { + error!("Failed to get settings: {err}"); Status::internal("unexpected error") })?; + debug!("Successfully retrived settings."); // send welcome email + debug!("Try to send welcome email..."); enrollment .send_welcome_email( &mut transaction, @@ -271,9 +339,13 @@ impl EnrollmentServer { .await?; // send success notification to admin + debug!( + "Trying to fetch admin data from the token to send notification about activating user." + ); let admin = enrollment.fetch_admin(&mut *transaction).await?; if let Some(admin) = admin { + debug!("Send admin notification mail."); Token::send_admin_notification( &self.mail_tx, &admin, @@ -283,13 +355,12 @@ impl EnrollmentServer { )?; } - transaction.commit().await.map_err(|_| { - error!("Failed to commit transaction"); + transaction.commit().await.map_err(|err| { + error!("Failed to commit transaction: {err}"); Status::internal("unexpected error") })?; info!("User {} activated", user.username); - Ok(()) } @@ -299,18 +370,29 @@ impl EnrollmentServer { req_device_info: Option, ) -> Result { debug!("Adding new user device: {request:?}"); - let enrollment = self.validate_session(request.token.as_deref()).await?; + let enrollment = self.validate_session(&request.token).await?; // fetch related users let user = enrollment.fetch_user(&self.pool).await?; // add device + debug!( + "Verifying if user {}({:?}) is active", + user.username, user.id + ); if !user.is_active { - error!("Can't create device for a disabled user {}", user.username); + error!( + "Can't create device for disabled user {}({:?})", + user.username, user.id + ); return Err(Status::invalid_argument( "can't add device to disabled user", )); } + info!( + "User {}({:?}) is active, proceeding with device creation, pubkey: {}", + user.username, user.id, request.pubkey + ); let ip_address; let device_info; @@ -322,41 +404,81 @@ impl EnrollmentServer { ip_address = String::new(); device_info = None; } + debug!("Ip address {}, device info {device_info:?}", ip_address); - Device::validate_pubkey(&request.pubkey).map_err(|_| { - error!("Invalid pubkey {}", request.pubkey); + debug!( + "Validating pubkey {} for device creation process for user {}({:?})", + request.pubkey, user.username, user.id, + ); + Device::validate_pubkey(&request.pubkey).map_err(|err| { + error!( + "Invalid pubkey {}, device won't be created for user {}({:?}): {err}", + request.pubkey, user.username, user.id + ); Status::invalid_argument("invalid pubkey") })?; + info!( + "Pubkey {} is valid for device creation process for user {}({:?})", + request.pubkey, user.username, user.id + ); // Make sure there is no device with the same pubkey, such state may lead to unexpected issues + debug!( + "Checking pubkey {} uniqueness for device creation process for user {}({:?}).", + request.pubkey, user.username, user.id, + ); if let Some(device) = Device::find_by_pubkey(&self.pool, &request.pubkey) .await - .map_err(|_| { - error!("Failed to get device by its pubkey: {}", request.pubkey); + .map_err(|err| { + error!( + "Failed to get device {} by its pubkey: {err}", + request.pubkey + ); Status::internal("unexpected error") })? { warn!( - "User {} failed to add device {}, identical pubkey ({}) already exists for device {}", + "User {}({:?}) failed to add device {}, identical pubkey ({}) already exists for device {}", user.username, + user.id, request.name, request.pubkey, device.name ); return Err(Status::invalid_argument("invalid key")); }; + info!( + "Pubkey {} is unique for device creation process for user {}({:?}).", + request.pubkey, user.username, user.id + ); - let mut device = Device::new(request.name, request.pubkey, enrollment.user_id); + let device = Device::new( + request.name.clone(), + request.pubkey.clone(), + enrollment.user_id, + ); + debug!( + "Creating new device for user {}({:?}) {device:?}.", + user.username, user.id, + ); - let mut transaction = self.pool.begin().await.map_err(|_| { - error!("Failed to begin transaction"); + let mut transaction = self.pool.begin().await.map_err(|err| { + error!("Failed to begin transaction: {err}"); Status::internal("unexpected error") })?; - device.save(&mut *transaction).await.map_err(|err| { - error!("Failed to save device {}: {err}", device.name); + let device = device.save(&mut *transaction).await.map_err(|err| { + error!( + "Failed to save device {}, pubkey {} for user {}({:?}): {err}", + request.name, request.pubkey, user.username, user.id, + ); Status::internal("unexpected error") })?; + info!("New device created: {device:?}."); + debug!( + "Adding device {} to all existing user networks for user {}({:?}).", + device.wireguard_pubkey, user.username, user.id, + ); let (network_info, configs) = device .add_to_all_networks(&mut transaction) @@ -369,20 +491,78 @@ impl EnrollmentServer { Status::internal("unexpected error") })?; + info!( + "Added device {} to all existing user networks for user {}({:?})", + device.wireguard_pubkey, user.username, user.id + ); + debug!( + "Sending DeviceCreated event to gateway for device {}, user {}({:?})", + device.wireguard_pubkey, user.username, user.id, + ); self.send_wireguard_event(GatewayEvent::DeviceCreated(DeviceInfo { device: device.clone(), network_info, })); + info!( + "Sent DeviceCreated event to gateway for device {}, user {}({:?})", + device.wireguard_pubkey, user.username, user.id, + ); + debug!( + "Fetching settings for device {} creation process for user {}({:?})", + device.wireguard_pubkey, user.username, user.id, + ); let settings = Settings::get_settings(&mut *transaction) .await - .map_err(|_| { - error!("Failed to get settings"); + .map_err(|err| { + error!( + "Failed to fetch settings for device {} creation process for user {}({:?}): {err}", + device.wireguard_pubkey, user.username, user.id, +); Status::internal("unexpected error") })?; + debug!("Settings: {settings:?}"); - transaction.commit().await.map_err(|_| { - error!("Failed to commit transaction"); + debug!( + "Fetching enterprise settings for device {} creation process for user {}({:?})", + device.wireguard_pubkey, user.username, user.id, + ); + let enterprise_settings = EnterpriseSettings::get(&mut *transaction) + .await + .map_err(|err| { + error!( + "Failed to fetch enterprise settings for device {} creation process for user {}({:?}): {err}", + device.wireguard_pubkey, user.username, user.id, + ); + Status::internal("unexpected error") + })?; + debug!("Enterprise settings: {enterprise_settings:?}"); + + // create polling token for further client communication + debug!( + "Creating polling token for further client communication for device {}, user {}({:?})", + device.wireguard_pubkey, user.username, user.id, + ); + let token = PollingToken::new(device.id) + .save(&mut *transaction) + .await + .map_err(|err| { + error!( + "Failed to save PollingToken for device {}, user {}({:?}): {err}", + device.wireguard_pubkey, user.username, user.id + ); + Status::internal("failed to save polling token") + })?; + info!( + "Created polling token for further client communication for device: {}, user {}({:?})", + device.wireguard_pubkey, user.username, user.id, + ); + + transaction.commit().await.map_err(|err| { + error!( + "Failed to commit transaction, device {} won't be created for user {}({:?}): {err}", + device.wireguard_pubkey, user.username, user.id, + ); Status::internal("unexpected error") })?; @@ -394,6 +574,10 @@ impl EnrollmentServer { }) .collect(); + debug!( + "Sending device created mail for device {}, user {}({:?})", + device.wireguard_pubkey, user.username, user.id + ); send_new_device_added_email( &device.name, &device.wireguard_pubkey, @@ -403,109 +587,40 @@ impl EnrollmentServer { Some(&ip_address), device_info.as_deref(), ) - .map_err(|_| Status::internal("Failed to render new device added template"))?; + .map_err(|_| Status::internal("error rendering email template"))?; info!( - "Device {} assigned to user {} and added to all networks.", - device.name, user.username + "Device {} assigned to user {}({:?}) and added to all networks.", + device.name, user.username, user.id, ); let response = DeviceConfigResponse { device: Some(device.into()), configs: configs.into_iter().map(Into::into).collect(), - instance: Some(InstanceInfo::new(settings, &user.username).into()), + instance: Some( + InstanceInfo::new(settings, &user.username, &enterprise_settings).into(), + ), + token: Some(token.token), }; + debug!("{response:?}."); Ok(response) } - /// Get all information needed - /// to update instance information for desktop client + /// Get all information needed to update instance information for desktop client pub async fn get_network_info( &self, request: ExistingDevice, ) -> Result { debug!("Getting network info for device: {:?}", request.pubkey); - let enrollment = self.validate_session(request.token.as_deref()).await?; + let _token = self.validate_session(&request.token).await?; - // get enrollment user - let user = enrollment.fetch_user(&self.pool).await?; - - Device::validate_pubkey(&request.pubkey).map_err(|_| { - error!("Invalid pubkey {}", request.pubkey); - Status::invalid_argument("invalid pubkey") - })?; - // Find existing device by public key - let device = Device::find_by_pubkey(&self.pool, &request.pubkey) - .await - .map_err(|_| { - error!("Failed to get device by its pubkey: {}", request.pubkey); - Status::internal("unexpected error") - })?; - - let settings = Settings::get_settings(&self.pool).await.map_err(|_| { - error!("Failed to get settings"); - Status::internal("unexpected error") - })?; - - let networks = WireguardNetwork::all(&self.pool).await.map_err(|err| { - error!("Failed to fetch all networks: {err}"); - Status::internal(format!("unexpected error: {err}")) - })?; - - let mut configs: Vec = Vec::new(); - if let Some(device) = device { - for network in networks { - let (Some(device_id), Some(network_id)) = (device.id, network.id) else { - continue; - }; - let wireguard_network_device = - WireguardNetworkDevice::find(&self.pool, device_id, network_id) - .await - .map_err(|err| { - error!("Failed to fetch wireguard network device for device {} and network {}: {err}", device_id, network_id); - Status::internal(format!("unexpected error: {err}")) - })?; - if let Some(wireguard_network_device) = wireguard_network_device { - let allowed_ips = network - .allowed_ips - .iter() - .map(IpNetwork::to_string) - .collect::>() - .join(","); - let config = ProtoDeviceConfig { - config: device.create_config(&network, &wireguard_network_device), - network_id, - network_name: network.name, - assigned_ip: wireguard_network_device.wireguard_ip.to_string(), - endpoint: format!("{}:{}", network.endpoint, network.port), - pubkey: network.pubkey, - allowed_ips, - dns: network.dns, - mfa_enabled: network.mfa_enabled, - keepalive_interval: network.keepalive_interval, - }; - configs.push(config); - } - } - - info!("Device {} configs fetched", device.name); - - let response = DeviceConfigResponse { - device: Some(device.into()), - configs, - instance: Some(InstanceInfo::new(settings, &user.username).into()), - }; - - Ok(response) - } else { - Err(Status::internal("device not found error")) - } + build_device_config_response(&self.pool, &request.pubkey, true).await } } -impl From for AdminInfo { - fn from(admin: User) -> Self { +impl From> for AdminInfo { + fn from(admin: User) -> Self { Self { name: format!("{} {}", admin.first_name, admin.last_name), phone_number: admin.phone, @@ -515,9 +630,9 @@ impl From for AdminInfo { } impl InitialUserInfo { - async fn from_user(pool: &DbPool, user: User) -> Result { - let is_enrolled = user.has_password(); - let devices = user.devices(pool).await?; + async fn from_user(pool: &PgPool, user: User) -> Result { + let enrolled = user.is_enrolled(); + let devices = user.user_devices(pool).await?; let device_names = devices.into_iter().map(|dev| dev.device.name).collect(); Ok(Self { first_name: user.first_name, @@ -527,7 +642,7 @@ impl InitialUserInfo { phone_number: user.phone, is_active: user.is_active, device_names, - enrolled: is_enrolled, + enrolled, }) } } @@ -555,10 +670,10 @@ impl From for ProtoDeviceConfig { } } -impl From for ProtoDevice { - fn from(device: Device) -> Self { +impl From> for ProtoDevice { + fn from(device: Device) -> Self { Self { - id: device.get_id().expect("Failed to get device ID"), + id: device.id, name: device.name, pubkey: device.wireguard_pubkey, user_id: device.user_id, @@ -573,7 +688,7 @@ impl Token { &self, transaction: &mut Transaction<'_, sqlx::Postgres>, mail_tx: &UnboundedSender, - user: &User, + user: &User, settings: &Settings, ip_address: &str, device_info: Option<&str>, @@ -603,8 +718,8 @@ impl Token { // Notify admin that a user has completed enrollment fn send_admin_notification( mail_tx: &UnboundedSender, - admin: &User, - user: &User, + admin: &User, + user: &User, ip_address: &str, device_info: Option<&str>, ) -> Result<(), TokenError> { diff --git a/src/grpc/gateway.rs b/src/grpc/gateway.rs index f97b1d8491..64125f34aa 100644 --- a/src/grpc/gateway.rs +++ b/src/grpc/gateway.rs @@ -5,7 +5,7 @@ use std::{ }; use chrono::{DateTime, Utc}; -use sqlx::{query, Error as SqlxError, PgExecutor}; +use sqlx::{query, Error as SqlxError, PgExecutor, PgPool}; use tokio::{ sync::{ broadcast::{Receiver as BroadcastReceiver, Sender}, @@ -20,7 +20,7 @@ use super::GatewayMap; use crate::{ db::{ models::wireguard::{WireguardNetwork, WireguardPeerStats}, - DbPool, Device, GatewayEvent, + Device, GatewayEvent, Id, NoId, }, mail::Mail, }; @@ -28,13 +28,13 @@ use crate::{ tonic::include_proto!("gateway"); pub struct GatewayServer { - pool: DbPool, + pool: PgPool, state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, } -impl WireguardNetwork { +impl WireguardNetwork { /// Get a list of all allowed peers /// /// Each device is marked as allowed or not allowed in a given network, @@ -43,10 +43,10 @@ impl WireguardNetwork { where E: PgExecutor<'e>, { - debug!("Fetching all peers for network {}", self.id.unwrap()); + debug!("Fetching all peers for network {}", self.id); let rows = query!( - "SELECT d.wireguard_pubkey as pubkey, preshared_key, \ - array[host(wnd.wireguard_ip)] as \"allowed_ips!: Vec\" \ + "SELECT d.wireguard_pubkey pubkey, preshared_key, \ + array[host(wnd.wireguard_ip)] \"allowed_ips!: Vec\" \ FROM wireguard_network_device wnd \ JOIN device d ON wnd.device_id = d.id \ JOIN \"user\" u ON d.user_id = u.id \ @@ -79,7 +79,7 @@ impl GatewayServer { /// Create new gateway server instance #[must_use] pub fn new( - pool: DbPool, + pool: PgPool, state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, @@ -134,7 +134,7 @@ impl GatewayServer { } } -fn gen_config(network: &WireguardNetwork, peers: Vec) -> Configuration { +fn gen_config(network: &WireguardNetwork, peers: Vec) -> Configuration { Configuration { name: network.name.clone(), port: network.port as u32, @@ -145,13 +145,13 @@ fn gen_config(network: &WireguardNetwork, peers: Vec) -> Configuration { } impl WireguardPeerStats { - fn from_peer_stats(stats: PeerStats, network_id: i64) -> Self { + fn from_peer_stats(stats: PeerStats, network_id: Id) -> Self { let endpoint = match stats.endpoint { endpoint if endpoint.is_empty() => None, _ => Some(stats.endpoint), }; Self { - id: None, + id: NoId, network: network_id, endpoint, device_id: -1, @@ -168,8 +168,8 @@ impl WireguardPeerStats { /// Helper struct for handling gateway events struct GatewayUpdatesHandler { - network_id: i64, - network: WireguardNetwork, + network_id: Id, + network: WireguardNetwork, gateway_hostname: String, events_rx: BroadcastReceiver, tx: mpsc::Sender>, @@ -177,8 +177,8 @@ struct GatewayUpdatesHandler { impl GatewayUpdatesHandler { pub fn new( - network_id: i64, - network: WireguardNetwork, + network_id: Id, + network: WireguardNetwork, gateway_hostname: String, events_rx: BroadcastReceiver, tx: mpsc::Sender>, @@ -313,7 +313,7 @@ impl GatewayUpdatesHandler { /// Sends updated network configuration async fn send_network_update( &self, - network: &WireguardNetwork, + network: &WireguardNetwork, peers: Vec, update_type: i32, ) -> Result<(), Status> { @@ -436,10 +436,10 @@ impl GatewayUpdatesHandler { pub struct GatewayUpdatesStream { task_handle: JoinHandle<()>, rx: Receiver>, - network_id: i64, + network_id: Id, gateway_hostname: String, gateway_state: Arc>, - pool: DbPool, + pool: PgPool, } impl GatewayUpdatesStream { @@ -447,10 +447,10 @@ impl GatewayUpdatesStream { pub fn new( task_handle: JoinHandle<()>, rx: Receiver>, - network_id: i64, + network_id: Id, gateway_hostname: String, gateway_state: Arc>, - pool: DbPool, + pool: PgPool, ) -> Self { Self { task_handle, @@ -507,15 +507,7 @@ impl gateway_service_server::GatewayService for GatewayServer { // Get device by public key and fill in stats.device_id // FIXME: keep an in-memory device map to avoid repeated DB requests stats.device_id = match Device::find_by_pubkey(&self.pool, &public_key).await { - Ok(Some(device)) => device.id.ok_or_else(|| { - Status::new( - Code::Internal, - format!( - "Device {} (public key: {public_key}) has no ID", - device.name - ), - ) - })?, + Ok(Some(device)) => device.id, Ok(None) => { error!("Device with public key {public_key} not found"); return Err(Status::new( @@ -532,15 +524,20 @@ impl gateway_service_server::GatewayService for GatewayServer { } }; // Save stats to db - if let Err(err) = stats.save(&self.pool).await { - error!("Saving WireGuard peer stats to db failed: {err}"); - return Err(Status::new( - Code::Internal, - format!("Saving WireGuard peer stats to db failed: {err}"), - )); - } - info!("Saved WireGuard peer stats to db: {stats:?}"); + let stats = match stats.save(&self.pool).await { + Ok(stats) => stats, + Err(err) => { + error!("Saving WireGuard peer stats to db failed: {err}"); + return Err(Status::new( + Code::Internal, + format!("Saving WireGuard peer stats to db failed: {err}"), + )); + } + }; + info!("Saved WireGuard peer stats to db."); + debug!("WireGuard peer stats: {stats:?}"); } + Ok(Response::new(())) } diff --git a/src/grpc/interceptor.rs b/src/grpc/interceptor.rs index 4872a327bb..190d0f09e8 100644 --- a/src/grpc/interceptor.rs +++ b/src/grpc/interceptor.rs @@ -1,8 +1,9 @@ -use crate::auth::{Claims, ClaimsType}; use tonic::{service::Interceptor, Status}; -/// Auth interceptor used by GRPC services. Verifies JWT token sent -/// in GRPC metadata under "authorization" key. +use crate::auth::{Claims, ClaimsType}; + +/// Auth interceptor used by gRPC services. Verifies JWT token sent +/// in gRPC metadata under "authorization" key. #[derive(Clone)] pub struct JwtInterceptor { claims_type: ClaimsType, diff --git a/src/grpc/mod.rs b/src/grpc/mod.rs index 784b40ce89..9f2fbcf1be 100644 --- a/src/grpc/mod.rs +++ b/src/grpc/mod.rs @@ -10,7 +10,10 @@ use std::{ }; use chrono::{Duration as ChronoDuration, NaiveDateTime, Utc}; +use reqwest::Url; use serde::Serialize; +#[cfg(feature = "worker")] +use sqlx::PgPool; use thiserror::Error; use tokio::{ sync::{ @@ -42,14 +45,19 @@ use self::{ worker::{worker_service_server::WorkerServiceServer, WorkerServer}, }; use crate::{ - auth::failed_login::FailedLoginMap, db::AppEvent, - handlers::mail::send_gateway_disconnected_email, mail::Mail, server_config, + auth::failed_login::FailedLoginMap, + db::{AppEvent, Id, Settings}, + enterprise::{ + db::models::enterprise_settings::EnterpriseSettings, + grpc::polling::PollingServer, + license::{get_cached_license, validate_license}, + }, + handlers::mail::send_gateway_disconnected_email, + mail::Mail, + server_config, }; #[cfg(feature = "worker")] -use crate::{ - auth::ClaimsType, - db::{DbPool, GatewayEvent}, -}; +use crate::{auth::ClaimsType, db::GatewayEvent}; mod auth; mod desktop_client_mfa; @@ -59,6 +67,7 @@ pub(crate) mod gateway; #[cfg(any(feature = "wireguard", feature = "worker"))] mod interceptor; pub mod password_reset; +pub(crate) mod utils; #[cfg(feature = "worker")] pub mod worker; @@ -70,10 +79,9 @@ use proto::{core_request, proxy_client::ProxyClient, CoreError, CoreResponse}; // Helper struct used to handle gateway state // gateways are grouped by network -type NetworkId = i64; type GatewayHostname = String; #[derive(Debug)] -pub struct GatewayMap(HashMap>); +pub struct GatewayMap(HashMap>); #[derive(Error, Debug)] pub enum GatewayMapError { @@ -100,7 +108,7 @@ impl GatewayMap { // as a sort of "registration" pub fn add_gateway( &mut self, - network_id: i64, + network_id: Id, network_name: &str, hostname: String, name: Option, @@ -120,7 +128,7 @@ impl GatewayMap { } // remove gateway from map - pub fn remove_gateway(&mut self, network_id: i64, uid: Uuid) -> Result<(), GatewayMapError> { + pub fn remove_gateway(&mut self, network_id: Id, uid: Uuid) -> Result<(), GatewayMapError> { debug!("Removing gateway from network {network_id}"); if let Some(network_gateway_map) = self.0.get_mut(&network_id) { // find gateway by uuid @@ -155,7 +163,7 @@ impl GatewayMap { // we assume that the gateway is already present in hashmap pub fn connect_gateway( &mut self, - network_id: i64, + network_id: Id, hostname: &str, ) -> Result<(), GatewayMapError> { debug!("Connecting gateway {hostname} in network {network_id}"); @@ -184,9 +192,9 @@ impl GatewayMap { // change gateway status to disconnected pub fn disconnect_gateway( &mut self, - network_id: i64, + network_id: Id, hostname: String, - pool: &DbPool, + pool: &PgPool, ) -> Result<(), GatewayMapError> { debug!("Disconnecting gateway {hostname} in network {network_id}"); if let Some(network_gateway_map) = self.0.get_mut(&network_id) { @@ -206,7 +214,7 @@ impl GatewayMap { // return `true` if at least one gateway in a given network is connected #[must_use] - pub fn connected(&self, network_id: i64) -> bool { + pub fn connected(&self, network_id: Id) -> bool { match self.0.get(&network_id) { Some(network_gateway_map) => network_gateway_map .values() @@ -217,7 +225,7 @@ impl GatewayMap { // return a list af aff statuses af all gateways in a given network #[must_use] - pub fn get_network_gateway_status(&self, network_id: i64) -> Vec { + pub fn get_network_gateway_status(&self, network_id: Id) -> Vec { match self.0.get(&network_id) { Some(network_gateway_map) => network_gateway_map.clone().into_values().collect(), None => Vec::new(), @@ -226,7 +234,7 @@ impl GatewayMap { // return gateway name #[must_use] - pub fn get_network_gateway_name(&self, network_id: i64, hostname: &str) -> Option { + pub fn get_network_gateway_name(&self, network_id: Id, hostname: &str) -> Option { match self.0.get(&network_id) { Some(network_gateway_map) => { if let Some(state) = network_gateway_map.get(hostname) { @@ -250,7 +258,7 @@ impl Default for GatewayMap { pub struct GatewayState { pub uid: Uuid, pub connected: bool, - pub network_id: i64, + pub network_id: Id, pub network_name: String, pub name: Option, pub hostname: String, @@ -265,7 +273,7 @@ pub struct GatewayState { impl GatewayState { #[must_use] pub fn new>( - network_id: i64, + network_id: Id, network_name: S, hostname: S, name: Option, @@ -287,7 +295,7 @@ impl GatewayState { /// Send gateway disconnected notification /// Sends notification only if last notification time is bigger than specified in config - fn send_disconnect_notification(&mut self, pool: &DbPool) { + fn send_disconnect_notification(&mut self, pool: &PgPool) { debug!("Sending gateway disconnect email notification"); // Clone here because self doesn't live long enough let name = self.name.clone(); @@ -340,7 +348,7 @@ impl From for CoreError { /// Bi-directional gRPC stream for comminication with Defguard proxy. pub async fn run_grpc_bidi_stream( - pool: DbPool, + pool: PgPool, wireguard_tx: Sender, mail_tx: UnboundedSender, user_agent_parser: Arc, @@ -355,7 +363,8 @@ pub async fn run_grpc_bidi_stream( user_agent_parser, ); let password_reset_server = PasswordResetServer::new(pool.clone(), mail_tx.clone()); - let mut client_mfa_server = ClientMfaServer::new(pool, mail_tx, wireguard_tx); + let mut client_mfa_server = ClientMfaServer::new(pool.clone(), mail_tx, wireguard_tx); + let polling_server = PollingServer::new(pool); let endpoint = Endpoint::from_shared(config.proxy_url.as_deref().unwrap())?; let endpoint = endpoint @@ -375,7 +384,10 @@ pub async fn run_grpc_bidi_stream( let mut client = ProxyClient::new(endpoint.connect_lazy()); let (tx, rx) = mpsc::unbounded_channel(); let Ok(response) = client.bidi(UnboundedReceiverStream::new(rx)).await else { - error!("Failed to connect to proxy, retrying in 10s"); + error!( + "Failed to connect to proxy @ {}, retrying in 10s", + endpoint.uri() + ); sleep(TEN_SECS).await; continue; }; @@ -388,7 +400,8 @@ pub async fn run_grpc_bidi_stream( break 'message; } Ok(Some(received)) => { - info!("Received message from proxy"); + info!("Received message from proxy."); + debug!("Received the following message from proxy: {received:?}"); let payload = match received.payload { // rpc StartEnrollment (EnrollmentStartRequest) returns (EnrollmentStartResponse) Some(core_request::Payload::EnrollmentStart(request)) => { @@ -504,6 +517,18 @@ pub async fn run_grpc_bidi_stream( } } } + // rpc LocationInfo (LocationInfoRequest) returns (LocationInfoResponse) + Some(core_request::Payload::InstanceInfo(request)) => { + match polling_server.info(request).await { + Ok(response_payload) => { + Some(core_response::Payload::InstanceInfo(response_payload)) + } + Err(err) => { + error!("Instance info error {err}"); + Some(core_response::Payload::CoreError(err.into())) + } + } + } // Reply without payload. None => None, }; @@ -514,6 +539,7 @@ pub async fn run_grpc_bidi_stream( tx.send(req).unwrap(); } Err(err) => { + error!("Disconnected from proxy at {}", endpoint.uri()); error!("stream error: {err}"); debug!("waiting 10s to re-establish the connection"); sleep(TEN_SECS).await; @@ -527,7 +553,7 @@ pub async fn run_grpc_bidi_stream( /// Runs gRPC server with core services. pub async fn run_grpc_server( worker_state: Arc>, - pool: DbPool, + pool: PgPool, gateway_state: Arc>, wireguard_tx: Sender, mail_tx: UnboundedSender, @@ -547,6 +573,12 @@ pub async fn run_grpc_server( GatewayServer::new(pool, gateway_state, wireguard_tx, mail_tx), JwtInterceptor::new(ClaimsType::Gateway), ); + + let (mut health_reporter, health_service) = tonic_health::server::health_reporter(); + health_reporter + .set_serving::>() + .await; + // Run gRPC server let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), server_config().grpc_port); debug!("Starting gRPC services"); @@ -559,6 +591,7 @@ pub async fn run_grpc_server( let router = builder .http2_keepalive_interval(Some(TEN_SECS)) .tcp_keepalive(Some(TEN_SECS)) + .add_service(health_service) .add_service(auth_service); #[cfg(feature = "wireguard")] let router = router.add_service(gateway_service); @@ -610,3 +643,47 @@ pub struct WorkerDetail { ip: IpAddr, connected: bool, } + +#[derive(Debug)] +pub struct InstanceInfo { + id: uuid::Uuid, + name: String, + url: Url, + proxy_url: Url, + username: String, + disable_all_traffic: bool, + enterprise_enabled: bool, +} + +impl InstanceInfo { + pub fn new>( + settings: Settings, + username: S, + enterprise_settings: &EnterpriseSettings, + ) -> Self { + let config = server_config(); + InstanceInfo { + id: settings.uuid, + name: settings.instance_name, + url: config.url.clone(), + proxy_url: config.enrollment_url.clone(), + username: username.into(), + disable_all_traffic: enterprise_settings.disable_all_traffic, + enterprise_enabled: validate_license(get_cached_license().as_ref()).is_ok(), + } + } +} + +impl From for proto::InstanceInfo { + fn from(instance: InstanceInfo) -> Self { + Self { + name: instance.name, + id: instance.id.to_string(), + url: instance.url.to_string(), + proxy_url: instance.proxy_url.to_string(), + username: instance.username, + disable_all_traffic: instance.disable_all_traffic, + enterprise_enabled: instance.enterprise_enabled, + } + } +} diff --git a/src/grpc/password_reset.rs b/src/grpc/password_reset.rs index d94c25c7c1..6a5516c16c 100644 --- a/src/grpc/password_reset.rs +++ b/src/grpc/password_reset.rs @@ -1,10 +1,15 @@ +use sqlx::PgPool; use tokio::sync::mpsc::UnboundedSender; use tonic::Status; +use super::proto::{ + DeviceInfo, PasswordResetInitializeRequest, PasswordResetRequest, PasswordResetStartRequest, + PasswordResetStartResponse, +}; use crate::{ db::{ models::enrollment::{Token, PASSWORD_RESET_TOKEN_TYPE}, - DbPool, User, + User, }, handlers::{ mail::{send_password_reset_email, send_password_reset_success_email}, @@ -15,20 +20,15 @@ use crate::{ server_config, }; -use super::proto::{ - PasswordResetInitializeRequest, PasswordResetRequest, PasswordResetStartRequest, - PasswordResetStartResponse, -}; - pub(super) struct PasswordResetServer { - pool: DbPool, + pool: PgPool, mail_tx: UnboundedSender, // ldap_feature_active: bool, } impl PasswordResetServer { #[must_use] - pub fn new(pool: DbPool, mail_tx: UnboundedSender) -> Self { + pub fn new(pool: PgPool, mail_tx: UnboundedSender) -> Self { // FIXME: check if LDAP feature is enabled // let ldap_feature_active = true; Self { @@ -38,33 +38,40 @@ impl PasswordResetServer { } } - // check if token provided with request corresponds to a valid enrollment session - async fn validate_session(&self, token: Option<&str>) -> Result { - debug!("Validating enrollment session"); + /// Checks if token provided with request corresponds to a valid password reset session + async fn validate_session(&self, token: &Option) -> Result { + info!("Validating password reset session. Token: {token:?}"); let Some(token) = token else { error!("Missing authorization header in request"); return Err(Status::unauthenticated("Missing authorization header")); }; - - debug!("Validating enrollment session token: {token}"); let enrollment = Token::find_by_id(&self.pool, token).await?; + debug!("Found matching token, verifying validity: {enrollment:?}."); + if !enrollment + .token_type + .as_ref() + .is_some_and(|token_type| token_type == PASSWORD_RESET_TOKEN_TYPE) + { + error!( + "Invalid token type used in password reset process: {:?}", + enrollment.token_type + ); + return Err(Status::permission_denied("invalid token")); + } if enrollment.is_session_valid(server_config().enrollment_session_timeout.as_secs()) { - info!( - "Enrollment session validated for user {}.", - enrollment.user_id - ); + info!("Password reset session validated: {enrollment:?}.",); Ok(enrollment) } else { - error!("Enrollment session expired"); - Err(Status::unauthenticated("Enrollment session expired")) + error!("Password reset session expired: {enrollment:?}"); + Err(Status::unauthenticated("Session expired")) } } pub async fn request_password_reset( &self, request: PasswordResetInitializeRequest, - req_device_info: Option, + req_device_info: Option, ) -> Result<(), Status> { let config = server_config(); debug!("Starting password reset request"); @@ -108,14 +115,10 @@ impl PasswordResetServer { Status::internal("unexpected error") })?; - Token::delete_unused_user_password_reset_tokens( - &mut transaction, - user.id.expect("Missing user ID"), - ) - .await?; + Token::delete_unused_user_password_reset_tokens(&mut transaction, user.id).await?; let enrollment = Token::new( - user.id.expect("Missing user ID"), + user.id, None, Some(email.clone()), config.password_reset_token_timeout.as_secs(), @@ -205,10 +208,10 @@ impl PasswordResetServer { pub async fn reset_password( &self, request: PasswordResetRequest, - req_device_info: Option, + req_device_info: Option, ) -> Result<(), Status> { debug!("Starting password reset: {request:?}"); - let enrollment = self.validate_session(request.token.as_deref()).await?; + let enrollment = self.validate_session(&request.token).await?; let ip_address; let user_agent; diff --git a/src/grpc/utils.rs b/src/grpc/utils.rs new file mode 100644 index 0000000000..8078075cf0 --- /dev/null +++ b/src/grpc/utils.rs @@ -0,0 +1,148 @@ +use ipnetwork::IpNetwork; +use sqlx::PgPool; +use tonic::Status; + +use super::{ + proto::{DeviceConfig as ProtoDeviceConfig, DeviceConfigResponse}, + InstanceInfo, +}; +use crate::{ + db::{ + models::{ + device::WireguardNetworkDevice, polling_token::PollingToken, + wireguard::WireguardNetwork, + }, + Device, Settings, User, + }, + enterprise::db::models::enterprise_settings::EnterpriseSettings, +}; + +pub(crate) async fn build_device_config_response( + pool: &PgPool, + pubkey: &str, + // Whether to make a new polling token for the device + new_token: bool, +) -> Result { + Device::validate_pubkey(pubkey).map_err(|_| { + error!("Invalid pubkey {pubkey}"); + Status::invalid_argument("invalid pubkey") + })?; + // Find existing device by public key + let device = Device::find_by_pubkey(pool, pubkey).await.map_err(|_| { + error!("Failed to get device by its pubkey: {pubkey}"); + Status::internal("unexpected error") + })?; + let settings = Settings::get_settings(pool).await.map_err(|_| { + error!("Failed to get settings"); + Status::internal("unexpected error") + })?; + + let networks = WireguardNetwork::all(pool).await.map_err(|err| { + error!("Failed to fetch all networks: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + + let enterprise_settings = EnterpriseSettings::get(pool).await.map_err(|err| { + error!("Failed to get enterprise settings: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + + let mut configs: Vec = Vec::new(); + let Some(device) = device else { + return Err(Status::internal("device not found error")); + }; + let user = User::find_by_id(pool, device.user_id) + .await + .map_err(|_| { + error!("Failed to get user: {}", device.user_id); + Status::internal("unexpected error") + })? + .ok_or_else(|| { + error!("User not found: {}", device.user_id); + Status::internal("unexpected error") + })?; + for network in networks { + let wireguard_network_device = WireguardNetworkDevice::find(pool, device.id, network.id) + .await + .map_err(|err| { + error!( + "Failed to fetch wireguard network device for device {} and network {}: {err}", + device.id, network.id + ); + Status::internal(format!("unexpected error: {err}")) + })?; + if let Some(wireguard_network_device) = wireguard_network_device { + let allowed_ips = network + .allowed_ips + .iter() + .map(IpNetwork::to_string) + .collect::>() + .join(","); + let config = ProtoDeviceConfig { + config: device.create_config(&network, &wireguard_network_device), + network_id: network.id, + network_name: network.name, + assigned_ip: wireguard_network_device.wireguard_ip.to_string(), + endpoint: format!("{}:{}", network.endpoint, network.port), + pubkey: network.pubkey, + allowed_ips, + dns: network.dns, + mfa_enabled: network.mfa_enabled, + keepalive_interval: network.keepalive_interval, + }; + configs.push(config); + } + } + + let token = if new_token { + debug!( + "Making a new polling token for device {}", + device.wireguard_pubkey + ); + let mut transaction = pool.begin().await.map_err(|err| { + error!("Failed to start transaction while making a new polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + + // 1. Delete existing polling token for the device, if it exists + // 2. Create a new polling token for the device + PollingToken::delete_for_device_id(&mut *transaction, device.id) + .await + .map_err(|err| { + error!("Failed to delete polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + let new_token = PollingToken::new(device.id) + .save(&mut *transaction) + .await + .map_err(|err| { + error!("Failed to save new polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + + transaction.commit().await.map_err(|err| { + error!("Failed to commit transaction while making a new polling token: {err}"); + Status::internal(format!("unexpected error: {err}")) + })?; + info!( + "New polling token created for device {}", + device.wireguard_pubkey + ); + + Some(new_token.token) + } else { + None + }; + + info!( + "User {}({:?}) device {}({:?}) config fetched", + user.username, user.id, device.name, device.id, + ); + + Ok(DeviceConfigResponse { + device: Some(device.into()), + configs, + instance: Some(InstanceInfo::new(settings, &user.username, &enterprise_settings).into()), + token, + }) +} diff --git a/src/grpc/worker.rs b/src/grpc/worker.rs index cf08b0d7b2..a81636278e 100644 --- a/src/grpc/worker.rs +++ b/src/grpc/worker.rs @@ -1,9 +1,3 @@ -use super::{Job, JobResponse, WorkerDetail, WorkerInfo, WorkerState}; -use crate::db::{ - models::authentication_key::{AuthenticationKey, AuthenticationKeyType}, - AppEvent, DbPool, HWKeyUserData, User, YubiKey, -}; -use sqlx::query; use std::{ collections::hash_map::{Entry, HashMap}, env, @@ -11,9 +5,17 @@ use std::{ sync::{Arc, Mutex}, time::Instant, }; + +use sqlx::{query, PgPool}; use tokio::sync::mpsc::UnboundedSender; use tonic::{Request, Response, Status}; +use super::{Job, JobResponse, WorkerDetail, WorkerInfo, WorkerState}; +use crate::db::{ + models::authentication_key::{AuthenticationKey, AuthenticationKeyType}, + AppEvent, HWKeyUserData, User, YubiKey, +}; + tonic::include_proto!("worker"); impl WorkerInfo { @@ -177,13 +179,13 @@ impl WorkerState { } pub struct WorkerServer { - pool: DbPool, + pool: PgPool, state: Arc>, } impl WorkerServer { #[must_use] - pub fn new(pool: DbPool, state: Arc>) -> Self { + pub fn new(pool: PgPool, state: Arc>) -> Self { Self { pool, state } } } @@ -258,54 +260,46 @@ impl worker_service_server::WorkerService for WorkerServer { Ok(Some(user)) => { // create yubikey // FIXME: pass name from user input this is temporary solution - if let Some(user_id) = user.id { - let yubi_count_res = query!( - "SELECT COUNT(*) FROM \"yubikey\" WHERE user_id = $1", - user.id - ) - .fetch_one(&self.pool) - .await - .map_err(|_| Status::internal("Failed to count keys"))?; - // FIXME: names may collide - let name = match yubi_count_res.count { - Some(count) => { - let name = format!("YubiKey {}", count + 1); - name - } - None => "YubiKey".to_string(), - }; - let mut new_yubi = YubiKey::new(name, message.yubikey_serial, user_id); - new_yubi - .save(&self.pool) - .await - .map_err(|_| Status::internal("Failed to save yubikey"))?; - if let Some(key_id) = new_yubi.id { - let mut ssh = AuthenticationKey::new( - user_id, - message.ssh_key, - None, - AuthenticationKeyType::Ssh, - Some(key_id), - ); - let mut gpg = AuthenticationKey::new( - user_id, - message.public_key, - None, - AuthenticationKeyType::Gpg, - Some(key_id), - ); - ssh.save(&self.pool) - .await - .map_err(|_| Status::internal("Failed to save auth key"))?; - gpg.save(&self.pool) - .await - .map_err(|_| Status::internal("Failed to save auth key"))?; - } else { - return Err(Status::internal("Yubikey did not get an id")); + let yubi_count_res = query!( + "SELECT COUNT(*) FROM \"yubikey\" WHERE user_id = $1", + user.id + ) + .fetch_one(&self.pool) + .await + .map_err(|_| Status::internal("Failed to count keys"))?; + // FIXME: names may collide + let name = match yubi_count_res.count { + Some(count) => { + let name = format!("YubiKey {}", count + 1); + name } - } else { - return Err(Status::internal("User has no ID")); - } + None => "YubiKey".to_string(), + }; + let new_yubi = YubiKey::new(name, message.yubikey_serial, user.id) + .save(&self.pool) + .await + .map_err(|_| Status::internal("Failed to save YubiKey"))?; + let key_id = new_yubi.id; + let ssh = AuthenticationKey::new( + user.id, + message.ssh_key, + None, + AuthenticationKeyType::Ssh, + Some(key_id), + ); + let gpg = AuthenticationKey::new( + user.id, + message.public_key, + None, + AuthenticationKeyType::Gpg, + Some(key_id), + ); + ssh.save(&self.pool) + .await + .map_err(|_| Status::internal("Failed to save auth key"))?; + gpg.save(&self.pool) + .await + .map_err(|_| Status::internal("Failed to save auth key"))?; } Ok(None) => info!("User {username} not found"), Err(err) => error!("Error {err}"), diff --git a/src/handlers/app_info.rs b/src/handlers/app_info.rs index fd3a66a091..a8bc4294c9 100644 --- a/src/handlers/app_info.rs +++ b/src/handlers/app_info.rs @@ -1,16 +1,22 @@ -use super::{ApiResponse, ApiResult, VERSION}; -use crate::{appstate::AppState, auth::SessionInfo, db::WireguardNetwork}; - -use crate::db::Settings; use axum::{extract::State, http::StatusCode}; use serde_json::json; +use super::{ApiResponse, ApiResult, VERSION}; +use crate::{ + appstate::AppState, + auth::SessionInfo, + db::{Settings, WireguardNetwork}, + enterprise::license::{get_cached_license, validate_license}, +}; + /// Additional information about core state. #[derive(Serialize)] pub struct AppInfo { version: String, network_present: bool, smtp_enabled: bool, + /// Whether the core has an enterprise license. + enterprise: bool, } pub(crate) async fn get_app_info( @@ -19,10 +25,13 @@ pub(crate) async fn get_app_info( ) -> ApiResult { let networks = WireguardNetwork::all(&appstate.pool).await?; let settings = Settings::get_settings(&appstate.pool).await?; + let license = get_cached_license(); + let enterprise = validate_license((license).as_ref()).is_ok(); let res = AppInfo { network_present: !networks.is_empty(), smtp_enabled: settings.smtp_configured(), version: VERSION.into(), + enterprise, }; Ok(ApiResponse::new(json!(res), StatusCode::OK)) diff --git a/src/handlers/auth.rs b/src/handlers/auth.rs index d5c076aac1..953ed2f346 100644 --- a/src/handlers/auth.rs +++ b/src/handlers/auth.rs @@ -74,14 +74,39 @@ pub async fn authenticate( } }, Ok(None) => { - // create user from LDAP - debug!("User not found in DB, authenticating user {username} with LDAP"); - if let Ok(user) = user_from_ldap(&appstate.pool, &username, &data.password).await { - user - } else { - info!("Failed to authenticate user {username} with LDAP"); - log_failed_login_attempt(&appstate.failed_logins, &username); - return Err(WebError::Authorization("user not found".into())); + match User::find_by_email(&appstate.pool, &username).await { + Ok(Some(user)) => match user.verify_password(&data.password) { + Ok(()) => { + if user.is_active { + user + } else { + info!("Failed to authenticate user {username}: user is disabled"); + return Err(WebError::Authorization("user not found".into())); + } + } + Err(err) => { + info!("Failed to authenticate user {username}: {err}"); + log_failed_login_attempt(&appstate.failed_logins, &username); + return Err(WebError::Authorization(err.to_string())); + } + }, + Ok(None) => { + // create user from LDAP + debug!("User not found in DB, authenticating user {username} with LDAP"); + if let Ok(user) = + user_from_ldap(&appstate.pool, &username, &data.password).await + { + user + } else { + info!("Failed to authenticate user {username} with LDAP"); + log_failed_login_attempt(&appstate.failed_logins, &username); + return Err(WebError::Authorization("user not found".into())); + } + } + Err(err) => { + error!("DB error when authenticating user {username}: {err}"); + return Err(WebError::DbError(err.to_string())); + } } } Err(err) => { @@ -104,7 +129,7 @@ pub async fn authenticate( debug!("Creating new session for user {username}"); let session = Session::new( - user.id.unwrap(), + user.id, SessionState::PasswordVerified, ip_address.clone(), device_info, @@ -258,8 +283,7 @@ pub async fn webauthn_init( user.username ); // passkeys to exclude - let passkeys = - WebAuthn::passkeys_for_user(&appstate.pool, user.id.expect("User ID missing")).await?; + let passkeys = WebAuthn::passkeys_for_user(&appstate.pool, user.id).await?; match appstate.webauthn.start_passkey_registration( Uuid::new_v4(), &user.username, @@ -330,7 +354,7 @@ pub async fn webauthn_finish( .await? .ok_or(WebError::WebauthnRegistration("User not found".into()))?; let recovery_codes = RecoveryCodes::new(user.get_recovery_codes(&appstate.pool).await?); - let mut webauthn = WebAuthn::new(session.session.user_id, webauth_reg.name, &passkey)?; + let webauthn = WebAuthn::new(session.session.user_id, webauth_reg.name, &passkey)?; webauthn.save(&appstate.pool).await?; if user.mfa_method == MFAMethod::None { send_mfa_configured_email( @@ -449,7 +473,7 @@ pub async fn totp_enable( ) -> ApiResult { let mut user = session.user; debug!("Enabling TOTP for user {}", user.username); - if user.verify_totp_code(data.code) { + if user.verify_totp_code(&data.code) { let recovery_codes = RecoveryCodes::new(user.get_recovery_codes(&appstate.pool).await?); user.enable_totp(&appstate.pool).await?; if user.mfa_method == MFAMethod::None { @@ -493,7 +517,7 @@ pub async fn totp_code( if let Some(user) = User::find_by_id(&appstate.pool, session.user_id).await? { let username = user.username.clone(); debug!("Verifying TOTP for user {}", username); - if user.totp_enabled && user.verify_totp_code(data.code) { + if user.totp_enabled && user.verify_totp_code(&data.code) { session .set_state(&appstate.pool, SessionState::MultiFactorVerified) .await?; @@ -562,7 +586,7 @@ pub async fn email_mfa_enable( ) -> ApiResult { let mut user = session.user; debug!("Enabling email MFA for user {}", user.username); - if user.verify_email_mfa_code(data.code) { + if user.verify_email_mfa_code(&data.code) { let recovery_codes = RecoveryCodes::new(user.get_recovery_codes(&appstate.pool).await?); user.enable_email_mfa(&appstate.pool).await?; if user.mfa_method == MFAMethod::None { @@ -628,7 +652,7 @@ pub async fn email_mfa_code( if let Some(user) = User::find_by_id(&appstate.pool, session.user_id).await? { let username = user.username.clone(); debug!("Verifying email MFA code for user {}", username); - if user.email_mfa_enabled && user.verify_email_mfa_code(data.code) { + if user.email_mfa_enabled && user.verify_email_mfa_code(&data.code) { session .set_state(&appstate.pool, SessionState::MultiFactorVerified) .await?; @@ -675,7 +699,7 @@ pub async fn web3auth_start( Json(data): Json, ) -> ApiResult { debug!("Starting web3 authentication for wallet {}", data.address); - match Settings::find_by_id(&appstate.pool, 1).await? { + match Settings::get(&appstate.pool).await? { Some(settings) => { let challenge = Wallet::format_challenge(&data.address, &settings.challenge_template); session diff --git a/src/handlers/group.rs b/src/handlers/group.rs index 748bdc5388..97f45dd46b 100644 --- a/src/handlers/group.rs +++ b/src/handlers/group.rs @@ -4,6 +4,7 @@ use axum::{ }; use serde_json::json; use sqlx::query_as; +use utoipa::ToSchema; use super::{ApiResponse, EditGroupInfo, GroupInfo, Username}; use crate::{ @@ -15,7 +16,7 @@ use crate::{ // ldap::utils::{ldap_add_user_to_group, ldap_modify_group, ldap_remove_user_from_group}, }; -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub(crate) struct Groups { groups: Vec, } @@ -27,7 +28,7 @@ impl Groups { } } -#[derive(Deserialize, Debug, Clone)] +#[derive(Deserialize, Debug, Clone, ToSchema)] pub(crate) struct BulkAssignToGroupsRequest { // groups by name groups: Vec, @@ -35,8 +36,23 @@ pub(crate) struct BulkAssignToGroupsRequest { users: Vec, } -// POST bulk assign users to one group for users overview -// assign many users to many groups at once +/// Bulk assign users to groups +/// +/// Assign many users to many groups at once. +/// +/// # Returns +/// If error occurs, it returns `WebError` object. +#[utoipa::path( + post, + path = "/api/v1/groups-assign", + responses( + (status = 200, description = "Successfully assign users to groups."), + (status = 400, description = "Bad request. Request contains users or groups that don't exist in db.", body = ApiResponse, example = json!({"msg": "Request contained users that doesn't exists in db."})), + (status = 401, description = "Unauthorized to assign users to groups.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to assign users to groups.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 500, description = "Cannot assign users to groups.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn bulk_assign_to_groups( _role: UserAdminRole, State(appstate): State, @@ -45,9 +61,9 @@ pub(crate) async fn bulk_assign_to_groups( debug!("Assigning groups to users."); let users = query_as!( User, - "SELECT id \"id?\", username, password_hash, last_name, first_name, email, \ + "SELECT id, username, password_hash, last_name, first_name, email, \ phone, mfa_enabled, totp_enabled, email_mfa_enabled, \ - totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active \ + totp_secret, email_mfa_secret, mfa_method \"mfa_method: _\", recovery_codes, is_active, openid_sub \ FROM \"user\" WHERE id = ANY($1)", &data.users ) @@ -83,13 +99,38 @@ pub(crate) async fn bulk_assign_to_groups( transaction.commit().await?; WireguardNetwork::sync_all_networks(&appstate).await?; info!("Assigned {} groups to {} users.", groups.len(), users.len()); + Ok(ApiResponse { json: json!({}), status: StatusCode::OK, }) } -/// GET: Retrieve all groups info +/// Retrieve all groups info +/// +/// For each group, the endpoint retrieves a `GroupInfo` object containing: group name, a list of members' usernames and a list of vpn_location. +/// +/// `There is another endpoint "/api/v1/group" that retrives only name of each groups if you don't want all information.` +/// +/// # Returns +/// Returns a list of `GroupInfo` objects or `WebError` if error occurs. +#[utoipa::path( + get, + path = "/api/v1/group-info", + responses( + (status = 200, description = "Successfully listed groups info.", body = [GroupInfo], example = json!([ + { + "name": "name", + "members": ["user"], + "vpn_locations": ["location"] + } + ])), + (status = 401, description = "Unauthorized to list groups info.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 401, description = "Unauthorized to assign users to groups.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to list groups info.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 500, description = "Cannot list groups info.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn list_groups_info( _role: UserAdminRole, State(appstate): State, @@ -97,9 +138,9 @@ pub(crate) async fn list_groups_info( debug!("Listing groups info"); let q_result = query_as!( GroupInfo, - "SELECT g.name as name, \ - COALESCE(ARRAY_AGG(DISTINCT u.username) FILTER (WHERE u.username IS NOT NULL), '{}') as \"members!\", \ - COALESCE(ARRAY_AGG(DISTINCT wn.name) FILTER (WHERE wn.name IS NOT NULL), '{}') as \"vpn_locations!\" \ + "SELECT g.name name, \ + COALESCE(ARRAY_AGG(DISTINCT u.username) FILTER (WHERE u.username IS NOT NULL), '{}') \"members!\", \ + COALESCE(ARRAY_AGG(DISTINCT wn.name) FILTER (WHERE wn.name IS NOT NULL), '{}') \"vpn_locations!\" \ FROM \"group\" g \ LEFT JOIN \"group_user\" gu ON gu.group_id = g.id \ LEFT JOIN \"user\" u ON u.id = gu.user_id \ @@ -115,7 +156,19 @@ pub(crate) async fn list_groups_info( }) } -/// GET: Retrieve all groups. +/// Retrieve all groups. +/// +/// # Returns +/// Returns a `Groups` object or `WebError` if error occurs. +#[utoipa::path( + get, + path = "/api/v1/group", + responses( + (status = 200, description = "Retrieve all groups.", body = Groups, example = json!({"groups": ["admin"]})), + (status = 401, description = "Unauthorized to retrive all groups.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 500, description = "Cannot retrive all groups.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn list_groups( _session: SessionInfo, State(appstate): State, @@ -133,7 +186,29 @@ pub(crate) async fn list_groups( }) } -/// GET: Retrieve group with `name`. +/// Retrieve group with `name`. +/// +/// # Returns +/// Returns a `GroupInfo` object or `WebError` if error occurs. +#[utoipa::path( + get, + path = "/api/v1/group/{name}", + params( + ("name" = String, description = "Group name") + ), + responses( + (status = 200, description = "Retrieve a group.", body = GroupInfo, example = json!( + { + "name": "name", + "members": ["user"], + "vpn_locations": ["location"] + } + )), + (status = 401, description = "Unauthorized to retrive a group.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 404, description = "Incorrect name of the group.", body = ApiResponse, example = json!({"msg": "Group not found"})), + (status = 500, description = "Cannot retrive a group.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn get_group( _session: SessionInfo, State(appstate): State, @@ -155,7 +230,29 @@ pub(crate) async fn get_group( } } -/// POST: Create group with a given name and member list. +/// Create group +/// +/// Create group with a given name and member list. +/// +/// # Returns +/// Returns a `GroupsInfo` object or `WebError` if error occurs. +#[utoipa::path( + post, + path = "/api/v1/group", + request_body = EditGroupInfo, + responses( + (status = 201, description = "Successfully created a group and added users.", body = EditGroupInfo, example = json!( + { + "name": "name", + "members": ["user"] + } + )), + (status = 401, description = "Unauthorized to retrive a group.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to list groups info.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Cannot create group: user don't exist.", body = ApiResponse, example = json!({"msg": "Failed to find user "})), + (status = 500, description = "Cannot retrive a group.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn create_group( _role: UserAdminRole, State(appstate): State, @@ -166,9 +263,8 @@ pub(crate) async fn create_group( // FIXME: LDAP operations are not reverted. let mut transaction = appstate.pool.begin().await?; - let mut group = Group::new(&group_info.name); // FIXME: conflicts must not return internal server error (500). - group.save(&appstate.pool).await?; + let group = Group::new(&group_info.name).save(&appstate.pool).await?; // TODO: create group in LDAP for username in &group_info.members { @@ -186,13 +282,31 @@ pub(crate) async fn create_group( WireguardNetwork::sync_all_networks(&appstate).await?; info!("Created group {}", group_info.name); + Ok(ApiResponse { json: json!(group_info), status: StatusCode::CREATED, }) } -/// PUT: Rename group and/or change group members. +/// Modify group +/// +/// Rename group and/or change group members. +/// +/// # Returns +/// Returns a `GroupsInfo` object or `WebError` if error occurs. +#[utoipa::path( + put, + path = "/api/v1/group/{name}", + request_body = EditGroupInfo, + responses( + (status = 201, description = "Successfully updated group."), + (status = 401, description = "Unauthorized to update user group.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to update user group.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Cannot update group: user or group don't exist.", body = ApiResponse, example = json!({"msg": "Group not found"})), + (status = 500, description = "Cannot update a group.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn modify_group( _role: UserAdminRole, State(appstate): State, @@ -249,10 +363,28 @@ pub(crate) async fn modify_group( Ok(ApiResponse::default()) } -/// DELETE: Remove group with `name`. +/// Remove group with `name`. +/// +/// Delete group and group members. +/// +/// # Returns +/// If error occurs it returns `WebError` object. +#[utoipa::path( + delete, + path = "/api/v1/group/{name}", + params( + ("name" = String, description = "Group name") + ), + responses( + (status = 200, description = "Successfully deleted a group."), + (status = 400, description = "Cannot delete admin group.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to delete group.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 404, description = "Cannot delete group: user or group don't exist.", body = ApiResponse, example = json!({"msg": "Failed to find group "})), + (status = 500, description = "Cannot delete a group.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn delete_group( _session: SessionInfo, - State(appstate): State, Path(name): Path, ) -> Result { @@ -282,7 +414,27 @@ pub(crate) async fn delete_group( } } -/// POST: Find a group with `name` and add `username` as a member. +/// Add a group member +/// +/// Find a group with `name` and add `username` as a member. +/// +/// # Returns +/// If error occurs it returns `WebError` object. +#[utoipa::path( + post, + path = "/api/v1/group/{name}", + params( + ("name" = String, description = "Group name") + ), + request_body = Username, + responses( + (status = 200, description = "Successfully add a new member to group."), + (status = 401, description = "Unauthorized to add a new group member.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to add a new group member.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Cannot add a new group member: user or group don't exist.", body = ApiResponse, example = json!({"msg": "Failed to find group "})), + (status = 500, description = "Cannot add a new group memmber.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn add_group_member( _role: UserAdminRole, State(appstate): State, @@ -311,7 +463,27 @@ pub(crate) async fn add_group_member( } } -/// DELETE: Remove `username` from group with `name`. +/// Remove `username` from group with `name`. +/// +/// Find a group with `name` and remove `username` as a member. +/// +/// # Returns +/// If error occurs it returns `WebError` object. +#[utoipa::path( + delete, + path = "/api/v1/group/{name}/user/{username}", + params( + ("name" = String, description = "Name of the group that you want to delete a user."), + ("username" = String, description = "Name of the user that you want to delete.") + ), + responses( + (status = 200, description = "Successfully remove a member from group.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to remove a group member.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to remove a group member.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Cannot remove a group member: user or group don't exist.", body = ApiResponse, example = json!({"msg": "Failed to find group "})), + (status = 500, description = "Cannot remove a group member.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub(crate) async fn remove_group_member( _role: UserAdminRole, State(appstate): State, diff --git a/src/handlers/mail.rs b/src/handlers/mail.rs index a687ca7450..f2cc35f904 100644 --- a/src/handlers/mail.rs +++ b/src/handlers/mail.rs @@ -17,13 +17,13 @@ use super::{ApiResponse, ApiResult}; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, - db::{models::enrollment::TokenError, MFAMethod, Session, User}, + db::{models::enrollment::TokenError, Id, MFAMethod, Session, User}, error::WebError, mail::{Attachment, Mail}, server_config, support::dump_config, templates::{self, support_data_mail, TemplateError, TemplateLocation}, - DbPool, + PgPool, }; static TEST_MAIL_SUBJECT: &str = "Defguard email test"; @@ -213,7 +213,7 @@ pub async fn send_gateway_disconnected_email( network_name: String, gateway_adress: &str, mail_tx: &UnboundedSender, - pool: &DbPool, + pool: &PgPool, ) -> Result<(), WebError> { debug!("Sending gateway disconnected mail to all admin users"); let admin_users = User::find_by_group_name(pool, &server_config().admin_groupname).await?; @@ -310,7 +310,7 @@ pub async fn send_new_device_ocid_login_email( pub fn send_mfa_configured_email( session: Option<&Session>, - user: &User, + user: &User, mfa_method: &MFAMethod, mail_tx: &UnboundedSender, ) -> Result<(), TemplateError> { @@ -341,7 +341,7 @@ pub fn send_mfa_configured_email( } pub fn send_email_mfa_activation_email( - user: &User, + user: &User, mail_tx: &UnboundedSender, session: &Session, ) -> Result<(), TemplateError> { @@ -356,7 +356,7 @@ pub fn send_email_mfa_activation_email( let mail = Mail { to: user.email.clone(), subject: EMAIL_MFA_ACTIVATION_EMAIL_SUBJECT.into(), - content: templates::email_mfa_activation_mail(code, session)?, + content: templates::email_mfa_activation_mail(user, &code, session)?, attachments: Vec::new(), result_tx: None, }; @@ -376,7 +376,7 @@ pub fn send_email_mfa_activation_email( } pub fn send_email_mfa_code_email( - user: &User, + user: &User, mail_tx: &UnboundedSender, session: Option<&Session>, ) -> Result<(), TemplateError> { @@ -391,7 +391,7 @@ pub fn send_email_mfa_code_email( let mail = Mail { to: user.email.clone(), subject: EMAIL_MFA_CODE_EMAIL_SUBJECT.into(), - content: templates::email_mfa_code_mail(code, session)?, + content: templates::email_mfa_code_mail(user, &code, session)?, attachments: Vec::new(), result_tx: None, }; @@ -411,7 +411,7 @@ pub fn send_email_mfa_code_email( } pub fn send_password_reset_email( - user: &User, + user: &User, mail_tx: &UnboundedSender, service_url: Url, token: &str, @@ -443,7 +443,7 @@ pub fn send_password_reset_email( } pub fn send_password_reset_success_email( - user: &User, + user: &User, mail_tx: &UnboundedSender, ip_address: Option<&str>, device_info: Option<&str>, diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index b66dfb54c3..55edbf4da0 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -4,13 +4,16 @@ use axum::{ Json, }; use serde_json::{json, Value}; +use sqlx::PgPool; +use utoipa::ToSchema; use webauthn_rs::prelude::RegisterPublicKeyCredential; #[cfg(feature = "wireguard")] use crate::db::Device; use crate::{ auth::SessionInfo, - db::{DbPool, User, UserInfo}, + db::{Id, NoId, User, UserInfo, WebHook}, + enterprise::license::LicenseError, error::WebError, VERSION, }; @@ -36,9 +39,9 @@ pub mod worker; pub(crate) mod yubikey; pub(crate) static SESSION_COOKIE_NAME: &str = "defguard_session"; -static SIGN_IN_COOKIE_NAME: &str = "defguard_sign_in"; +pub(crate) static SIGN_IN_COOKIE_NAME: &str = "defguard_sign_in"; -#[derive(Default)] +#[derive(Default, ToSchema)] pub struct ApiResponse { pub json: Value, pub status: StatusCode, @@ -104,6 +107,34 @@ impl From for ApiResponse { StatusCode::INTERNAL_SERVER_ERROR, ) } + WebError::LicenseError(err) => match err { + LicenseError::DecodeError(msg) | LicenseError::InvalidLicense(msg) => { + warn!(msg); + ApiResponse::new(json!({ "msg": msg }), StatusCode::BAD_REQUEST) + } + LicenseError::SignatureMismatch => { + let msg = "License signature doesn't match its content"; + warn!(msg); + ApiResponse::new(json!({ "msg": msg }), StatusCode::BAD_REQUEST) + } + LicenseError::InvalidSignature => { + let msg = "License signature is malformed and couldn't be read"; + warn!(msg); + ApiResponse::new(json!({ "msg": msg }), StatusCode::BAD_REQUEST) + } + LicenseError::LicenseNotFound => { + let msg = "License not found"; + warn!(msg); + ApiResponse::new(json!({ "msg": msg }), StatusCode::NOT_FOUND) + } + _ => { + error!("License error: {err}"); + ApiResponse::new( + json!({"msg": "Internal server error"}), + StatusCode::FORBIDDEN, + ) + } + }, } } } @@ -161,17 +192,17 @@ impl AuthTotp { #[derive(Deserialize, Serialize)] pub struct AuthCode { - code: u32, + code: String, } impl AuthCode { #[must_use] - pub fn new(code: u32) -> Self { - Self { code } + pub fn new>(code: S) -> Self { + Self { code: code.into() } } } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct GroupInfo { pub name: String, pub members: Vec, @@ -190,13 +221,13 @@ impl GroupInfo { } /// Dedicated `GroupInfo` variant for group modification operations. -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct EditGroupInfo { pub name: String, pub members: Vec, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct Username { pub username: String, } @@ -211,37 +242,37 @@ pub struct AddUserData { pub password: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, ToSchema)] pub struct StartEnrollmentRequest { #[serde(default)] pub send_enrollment_notification: bool, pub email: Option, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct PasswordChangeSelf { pub old_password: String, pub new_password: String, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct PasswordChange { pub new_password: String, } -#[derive(Deserialize)] +#[derive(Deserialize, ToSchema)] pub struct WalletSignature { pub address: String, pub signature: String, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct WalletChallenge { - pub id: i64, + pub id: Id, pub message: String, } -#[derive(Deserialize)] +#[derive(Deserialize, ToSchema)] pub struct WalletChange { pub use_for_mfa: bool, } @@ -274,6 +305,34 @@ impl RecoveryCodes { } } +#[derive(Deserialize)] +pub struct WebHookData { + pub url: String, + pub description: String, + pub token: String, + pub enabled: bool, + pub on_user_created: bool, + pub on_user_deleted: bool, + pub on_user_modified: bool, + pub on_hwkey_provision: bool, +} + +impl From for WebHook { + fn from(data: WebHookData) -> Self { + Self { + id: NoId, + url: data.url, + description: data.description, + token: data.token, + enabled: data.enabled, + on_user_created: data.on_user_created, + on_user_deleted: data.on_user_deleted, + on_user_modified: data.on_user_modified, + on_hwkey_provision: data.on_hwkey_provision, + } + } +} + /// Return type needed to know if user came from openid flow /// with optional url to redirect him later if yes #[derive(Serialize, Deserialize)] @@ -285,18 +344,25 @@ pub struct AuthResponse { /// Try to fetch [`User`] if the username is of the currently logged in user, or /// the logged in user is an admin. pub async fn user_for_admin_or_self( - pool: &DbPool, + pool: &PgPool, session: &SessionInfo, username: &str, -) -> Result { +) -> Result, WebError> { if session.user.username == username || session.is_admin { - match User::find_by_username(pool, username).await? { - Some(user) => Ok(user), - None => Err(WebError::ObjectNotFound(format!( + debug!("The user meets one or both of these conditions: 1) the user from the current session has admin privileges, 2) the user performs this operation on themself."); + if let Some(user) = User::find_by_username(pool, username).await? { + debug!("User {} has been found in database.", user.username); + Ok(user) + } else { + debug!("User with {username} does not exist in database."); + Err(WebError::ObjectNotFound(format!( "user {username} not found" - ))), + ))) } } else { + debug!( + "User from the current session doesn't have enough privileges to do this operation." + ); Err(WebError::Forbidden("requires privileged access".into())) } } @@ -305,10 +371,10 @@ pub async fn user_for_admin_or_self( /// the logged in user is an admin. #[cfg(feature = "wireguard")] pub async fn device_for_admin_or_self( - pool: &DbPool, + pool: &PgPool, session: &SessionInfo, - id: i64, -) -> Result { + id: Id, +) -> Result, WebError> { let fetch = if session.is_admin { Device::find_by_id(pool, id).await } else { diff --git a/src/handlers/openid_clients.rs b/src/handlers/openid_clients.rs index 50ec5dedf1..b9d2132ea5 100644 --- a/src/handlers/openid_clients.rs +++ b/src/handlers/openid_clients.rs @@ -20,12 +20,11 @@ pub async fn add_openid_client( State(appstate): State, Json(data): Json, ) -> ApiResult { - let mut client = OAuth2Client::from_new(data); + let client = OAuth2Client::from_new(data).save(&appstate.pool).await?; debug!( "User {} adding OpenID client {}", session.user.username, client.name ); - client.save(&appstate.pool).await?; info!( "User {} added OpenID client {}", session.user.username, client.name @@ -78,7 +77,7 @@ pub async fn change_openid_client( Json(data): Json, ) -> ApiResult { debug!( - "User {} updating OpenID client {client_id}", + "User {} updating OpenID client {client_id}...", session.user.username ); let status = match OAuth2Client::find_by_client_id(&appstate.pool, &client_id).await? { diff --git a/src/handlers/openid_flow.rs b/src/handlers/openid_flow.rs index d48c048867..0c166ed25d 100644 --- a/src/handlers/openid_flow.rs +++ b/src/handlers/openid_flow.rs @@ -32,6 +32,7 @@ use serde::{ ser::{Serialize, Serializer}, }; use serde_json::json; +use sqlx::PgPool; use time::Duration; use super::{ApiResponse, ApiResult, SESSION_COOKIE_NAME}; @@ -40,7 +41,7 @@ use crate::{ auth::{AccessUserInfo, SessionInfo}, db::{ models::{auth_code::AuthCode, oauth2client::OAuth2Client}, - DbPool, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User, + Id, OAuth2AuthorizedApp, OAuth2Token, Session, SessionState, User, }, error::WebError, handlers::{mail::send_new_device_ocid_login_email, SIGN_IN_COOKIE_NAME}, @@ -48,8 +49,8 @@ use crate::{ }; /// https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims -impl From<&User> for StandardClaims { - fn from(user: &User) -> StandardClaims { +impl From<&User> for StandardClaims { + fn from(user: &User) -> StandardClaims { let mut name = LocalizedClaim::new(); name.insert(None, EndUserName::new(user.name())); let mut given_name = LocalizedClaim::new(); @@ -96,7 +97,7 @@ pub type DefguardTokenResponse = StandardTokenResponse FromRequestParts for OAuth2Client +impl FromRequestParts for OAuth2Client where S: Send + Sync, AppState: FromRef, @@ -225,7 +226,7 @@ pub struct AuthenticationRequest { impl AuthenticationRequest { fn validate_for_client( &self, - oauth2client: &OAuth2Client, + oauth2client: &OAuth2Client, ) -> Result<(), CoreAuthErrorResponseType> { // check scope: it is valid if any requested scope exists in the `oauth2client` if self @@ -285,6 +286,7 @@ impl AuthenticationRequest { } info!("Validation succeeded for client {}", oauth2client.name); + Ok(()) } } @@ -293,19 +295,20 @@ impl AuthenticationRequest { async fn generate_auth_code_redirect( appstate: AppState, data: AuthenticationRequest, - user_id: Option, + user_id: Id, ) -> Result { let mut url = Url::parse(&data.redirect_uri).map_err(|_| WebError::Http(StatusCode::BAD_REQUEST))?; - let mut auth_code = AuthCode::new( - user_id.unwrap(), + let auth_code = AuthCode::new( + user_id, data.client_id, data.redirect_uri, data.scope, data.nonce, data.code_challenge, - ); - auth_code.save(&appstate.pool).await?; + ) + .save(&appstate.pool) + .await?; { let mut query_pairs = url.query_pairs_mut(); @@ -424,7 +427,7 @@ pub async fn authorization( OAuth2AuthorizedApp::find_by_user_and_oauth2client_id( &appstate.pool, session.user_id, - oauth2client.id.unwrap(), + oauth2client.id, ) .await? { @@ -437,7 +440,7 @@ pub async fn authorization( let location = generate_auth_code_redirect( appstate, data, - Some(session.user_id), + session.user_id, ) .await?; Ok(redirect_to(location, private_cookies)) @@ -445,7 +448,7 @@ pub async fn authorization( // If authorized app not found redirect to consent form info!( "OAuth client id {} not yet authorized by user id {}, redirecting to consent form", - oauth2client.id.unwrap(), session.user_id + oauth2client.id, session.user_id ); Ok(redirect_to( format!( @@ -508,7 +511,7 @@ pub struct GroupClaims { impl AdditionalClaims for GroupClaims {} -pub async fn get_group_claims(pool: &DbPool, user: &User) -> Result { +pub async fn get_group_claims(pool: &PgPool, user: &User) -> Result { let groups = user.member_of_names(pool).await?; Ok(GroupClaims { groups: Some(groups), @@ -533,16 +536,13 @@ pub async fn secure_authorization( Ok(()) => { if OAuth2AuthorizedApp::find_by_user_and_oauth2client_id( &appstate.pool, - session_info.user.id.unwrap(), - oauth2client.id.unwrap(), + session_info.user.id, + oauth2client.id, ) .await? .is_none() { - let mut app = OAuth2AuthorizedApp::new( - session_info.user.id.unwrap(), - oauth2client.id.unwrap(), - ); + let app = OAuth2AuthorizedApp::new(session_info.user.id, oauth2client.id); app.save(&appstate.pool).await?; send_new_device_ocid_login_email( @@ -636,7 +636,7 @@ impl TokenRequest { fn authorization_code_flow( &self, - auth_code: &AuthCode, + auth_code: &AuthCode, token: &OAuth2Token, claims: StandardClaims, base_url: &Url, @@ -738,7 +738,7 @@ impl TokenRequest { token_response } - async fn oauth2client(&self, pool: &DbPool) -> Option { + async fn oauth2client(&self, pool: &PgPool) -> Option> { if let (Some(client_id), Some(client_secret)) = (self.client_id.as_ref(), self.client_secret.as_ref()) { @@ -757,7 +757,7 @@ impl TokenRequest { /// https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokens pub async fn token( State(appstate): State, - oauth2client: Option, + oauth2client: Option>, Form(form): Form, ) -> ApiResult { // TODO: cleanup branches @@ -788,8 +788,8 @@ pub async fn token( if let Some(authorized_app) = OAuth2AuthorizedApp::find_by_user_and_oauth2client_id( &appstate.pool, - user.id.unwrap(), - client.id.unwrap(), + user.id, + client.id, ) .await? { @@ -800,14 +800,14 @@ pub async fn token( // Remove existing token in case same client asks for new token if let Some(token) = OAuth2Token::find_by_authorized_app_id( &appstate.pool, - authorized_app.id.unwrap(), + authorized_app.id, ) .await? { token.delete(&appstate.pool).await?; } let token = OAuth2Token::new( - authorized_app.id.unwrap(), + authorized_app.id, auth_code.redirect_uri.clone(), auth_code.scope.clone(), ); diff --git a/src/handlers/settings.rs b/src/handlers/settings.rs index 1185317849..90ccc76bbe 100644 --- a/src/handlers/settings.rs +++ b/src/handlers/settings.rs @@ -12,6 +12,7 @@ use crate::{ models::settings::{SettingsEssentials, SettingsPatch}, Settings, }, + enterprise::license::update_cached_license, error::WebError, ldap::LDAPConnection, AppState, @@ -22,7 +23,7 @@ static DEFAULT_MAIN_LOGO_URL: &str = "/svg/logo-defguard-white.svg"; pub async fn get_settings(State(appstate): State) -> ApiResult { debug!("Retrieving settings"); - if let Some(mut settings) = Settings::find_by_id(&appstate.pool, 1).await? { + if let Some(mut settings) = Settings::get(&appstate.pool).await? { if settings.nav_logo_url.is_empty() { settings.nav_logo_url = DEFAULT_NAV_LOGO_URL.into(); } @@ -45,12 +46,15 @@ pub async fn update_settings( _admin: AdminRole, session: SessionInfo, State(appstate): State, - Json(mut data): Json, + Json(data): Json, ) -> ApiResult { debug!("User {} updating settings", session.user.username); - data.id = Some(1); + + update_cached_license(data.license.as_deref())?; data.save(&appstate.pool).await?; + info!("User {} updated settings", session.user.username); + Ok(ApiResponse::default()) } @@ -63,7 +67,9 @@ pub async fn get_settings_essentials(State(appstate): State) -> ApiRes if settings.main_logo_url.is_empty() { settings.main_logo_url = DEFAULT_MAIN_LOGO_URL.into(); } + info!("Retrieved essential settings"); + Ok(ApiResponse { json: json!(settings), status: StatusCode::OK, @@ -73,14 +79,14 @@ pub async fn get_settings_essentials(State(appstate): State) -> ApiRes pub async fn set_default_branding( _admin: AdminRole, State(appstate): State, - Path(id): Path, + Path(_id): Path, // TODO: check with front-end and remove. session: SessionInfo, ) -> ApiResult { debug!( "User {} restoring default branding settings", session.user.username ); - let settings = Settings::find_by_id(&appstate.pool, id).await?; + let settings = Settings::get(&appstate.pool).await?; match settings { Some(mut settings) => { settings.instance_name = "Defguard".into(); @@ -106,11 +112,18 @@ pub async fn patch_settings( session: SessionInfo, Json(data): Json, ) -> ApiResult { - debug!("Admin {} patching settings.", &session.user.username); + debug!("Admin {} patching settings.", session.user.username); let mut settings = Settings::get_settings(&appstate.pool).await?; + + // Handle updating the cached license + if let Some(license_key) = &data.license { + update_cached_license(license_key.as_deref())?; + debug!("Saving the new license key to the database as part of the settings patch"); + }; + settings.apply(data); settings.save(&appstate.pool).await?; - info!("Admin {} patched settings.", &session.user.username); + info!("Admin {} patched settings.", session.user.username); Ok(ApiResponse::default()) } diff --git a/src/handlers/ssh_authorized_keys.rs b/src/handlers/ssh_authorized_keys.rs index fd2f69f963..9db9cfabef 100644 --- a/src/handlers/ssh_authorized_keys.rs +++ b/src/handlers/ssh_authorized_keys.rs @@ -4,7 +4,7 @@ use axum::{ Json, }; use serde_json::json; -use sqlx::{query, Error as SqlxError, PgExecutor}; +use sqlx::{query, Error as SqlxError, PgExecutor, PgPool}; use ssh_key::PublicKey; use super::{user_for_admin_or_self, ApiResponse, ApiResult}; @@ -13,30 +13,30 @@ use crate::{ auth::SessionInfo, db::{ models::authentication_key::{AuthenticationKey, AuthenticationKeyType}, - DbPool, Group, User, + Group, Id, User, }, error::WebError, }; #[derive(Deserialize, Serialize)] pub(crate) struct AuthenticationKeyInfo { - id: i64, + id: Id, name: Option, key_type: AuthenticationKeyType, key: String, - user_id: i64, + user_id: Id, yubikey_serial: Option, yubikey_id: Option, yubikey_name: Option, } impl AuthenticationKeyInfo { - pub async fn find_by_user_id<'e, E>(executor: E, user_id: i64) -> Result, SqlxError> + pub async fn find_by_user_id<'e, E>(executor: E, user_id: Id) -> Result, SqlxError> where E: PgExecutor<'e>, { let q_res = query!( - "SELECT k.id as key_id, k.name, k.key_type \"key_type: AuthenticationKeyType\", \ + "SELECT k.id key_id, k.name, k.key_type \"key_type: AuthenticationKeyType\", \ k.key, k.user_id, k.yubikey_id, \ y.name \"yubikey_name: Option\", y.serial \"serial: Option\" \ FROM \"authentication_key\" k \ @@ -59,23 +59,21 @@ impl AuthenticationKeyInfo { yubikey_serial: q.serial.clone(), }) .collect(); + Ok(res) } } -async fn add_user_ssh_keys_to_list(pool: &DbPool, user: &User, ssh_keys: &mut Vec) { - if let Some(user_id) = user.id { - let keys_result = - AuthenticationKey::find_by_user_id(pool, user_id, Some(AuthenticationKeyType::Ssh)) - .await; +async fn add_user_ssh_keys_to_list(pool: &PgPool, user: &User, ssh_keys: &mut Vec) { + let keys_result = + AuthenticationKey::find_by_user_id(pool, user.id, Some(AuthenticationKeyType::Ssh)).await; - if let Ok(authentication_keys) = keys_result { - let mut keys: Vec = authentication_keys - .into_iter() - .map(|item| item.key) - .collect(); - ssh_keys.append(&mut keys); - } + if let Ok(authentication_keys) = keys_result { + let mut keys: Vec = authentication_keys + .into_iter() + .map(|item| item.key) + .collect(); + ssh_keys.append(&mut keys); } } @@ -173,12 +171,8 @@ pub async fn add_authentication_key( // authorize request let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let Some(user_id) = user.id else { - error!("Model returned user ({}) without ID", user.username); - return Err(WebError::ModelError("Model returned without ID".into())); - }; - let trimmed_key = data.key.trim_end_matches(|c| c == '\n' || c == '\r'); + let trimmed_key = data.key.trim_end_matches(['\n', '\r']); // verify key match data.key_type { @@ -196,7 +190,7 @@ pub async fn add_authentication_key( // check if exists let exists_res = query!( "SELECT COUNT(1) FROM \"authentication_key\" WHERE user_id = $1 AND key = $2", - user_id, + user.id, trimmed_key, ) .fetch_one(&appstate.pool) @@ -205,19 +199,22 @@ pub async fn add_authentication_key( error!("User {username} tried to insert existing key: {data:?}"); return Err(WebError::BadRequest("Key already exists.".into())); } - let mut new_key = AuthenticationKey::new( - user_id, + + AuthenticationKey::new( + user.id, trimmed_key.to_string(), Some(data.name.clone()), data.key_type.clone(), None, - ); - new_key.save(&appstate.pool).await?; + ) + .save(&appstate.pool) + .await?; info!( "Added new key \"{}\" of type {:?} for user {username}", data.name, data.key_type ); + Ok(ApiResponse { json: json!({}), status: StatusCode::CREATED, @@ -231,14 +228,7 @@ pub async fn fetch_authentication_keys( session: SessionInfo, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let Some(user_id) = user.id else { - error!("Model returned user ({}) without ID", user.username); - return Err(WebError::ModelError( - "Model returned user without ID".into(), - )); - }; - - let keys_info = AuthenticationKeyInfo::find_by_user_id(&appstate.pool, user_id).await?; + let keys_info = AuthenticationKeyInfo::find_by_user_id(&appstate.pool, user.id).await?; Ok(ApiResponse { json: json!(keys_info), @@ -252,11 +242,8 @@ pub async fn delete_authentication_key( Path((username, key_id)): Path<(String, i64)>, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let user_id = user - .id - .ok_or(WebError::DbError("Returned user had no ID".into()))?; if let Some(key) = AuthenticationKey::find_by_id(&appstate.pool, key_id).await? { - if !session.is_admin && user_id != key.user_id { + if !session.is_admin && user.id != key.user_id { return Err(WebError::Forbidden(String::new())); } key.delete(&appstate.pool).await?; @@ -264,6 +251,7 @@ pub async fn delete_authentication_key( error!("Key with id {} not found", key_id); return Err(WebError::BadRequest("Key not found".into())); } + Ok(ApiResponse { json: json!({}), status: StatusCode::OK, @@ -282,9 +270,6 @@ pub async fn rename_authentication_key( Json(data): Json, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let user_id = user - .id - .ok_or(WebError::DbError("Returned user had no ID".into()))?; if let Some(mut key) = AuthenticationKey::find_by_id(&appstate.pool, key_id).await? { if key.yubikey_id.is_some() { warn!( @@ -293,7 +278,7 @@ pub async fn rename_authentication_key( ); return Err(WebError::BadRequest("Rename yubikey instead.".into())); } - if !session.is_admin && user_id != key.user_id { + if !session.is_admin && user.id != key.user_id { warn!( "User {} tried to rename key ({}) of another user with id {}", username, key_id, key.user_id diff --git a/src/handlers/user.rs b/src/handlers/user.rs index 44608a054c..51e88154f4 100644 --- a/src/handlers/user.rs +++ b/src/handlers/user.rs @@ -3,6 +3,7 @@ use axum::{ http::StatusCode, }; use serde_json::json; +use utoipa::ToSchema; use super::{ mail::{send_mfa_configured_email, EMAIL_PASSOWRD_RESET_START_SUBJECT}, @@ -14,9 +15,12 @@ use crate::{ appstate::AppState, auth::{SessionInfo, UserAdminRole}, db::{ - models::enrollment::{Token, PASSWORD_RESET_TOKEN_TYPE}, - AppEvent, MFAMethod, OAuth2AuthorizedApp, Settings, User, UserDetails, UserInfo, Wallet, - WebAuthn, WireguardNetwork, + models::{ + device::DeviceInfo, + enrollment::{Token, PASSWORD_RESET_TOKEN_TYPE}, + }, + AppEvent, GatewayEvent, MFAMethod, OAuth2AuthorizedApp, Settings, User, UserDetails, + UserInfo, Wallet, WebAuthn, WireguardNetwork, }, error::WebError, ldap::utils::{ldap_add_user, ldap_change_password, ldap_delete_user, ldap_modify_user}, @@ -34,7 +38,7 @@ use crate::{ /// - starts with non-special character /// - special characters: . - _ /// - no whitespaces -fn check_username(username: &str) -> Result<(), WebError> { +pub fn check_username(username: &str) -> Result<(), WebError> { // check length let length = username.len(); if !(3..64).contains(&length) { @@ -65,6 +69,33 @@ fn check_username(username: &str) -> Result<(), WebError> { Ok(()) } +/// Prune the given username from illegal characters in accordance with the following rules: +/// +/// To enable LDAP sync usernames need to avoid reserved characters. +/// Username requirements: +/// - 64 characters long +/// - only lowercase or uppercase latin alphabet letters (A-Z, a-z) and digits (0-9) +/// - starts with non-special character +/// - only special characters allowed: . - _ +/// - no whitespaces +pub fn prune_username(username: &str) -> String { + let mut result = username.to_string(); + + if result.len() > 64 { + result.truncate(64); + } + + // Go through the string and remove any non-alphanumeric characters at the beginning + result = result + .trim_start_matches(|c: char| !c.is_ascii_alphanumeric()) + .to_string(); + + result.retain(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_'); + result = result.replace(' ', ""); + + result +} + pub(crate) fn check_password_strength(password: &str) -> Result<(), WebError> { if !(8..=128).contains(&password.len()) { return Err(WebError::Serialization("Incorrect password length".into())); @@ -90,6 +121,42 @@ pub(crate) fn check_password_strength(password: &str) -> Result<(), WebError> { Ok(()) } +/// List of all users +/// +/// Retrives list of users. +/// +/// # Returns +/// Returns list of `UserInfo` objects or `WebError` if error occurs. +#[utoipa::path( + get, + path = "/api/v1/user", + responses( + (status = 200, description = "List of all users.", body = [UserInfo], example = json!( + [ + { + "authorized_apps": [], + "email": "name@email.com", + "email_mfa_enabled": false, + "enrolled": true, + "first_name": "first_name", + "groups": [ + "group" + ], + "id": 1, + "is_active": true, + "last_name": "last_name", + "mfa_enabled": false, + "mfa_method": "None", + "phone": null, + "totp_enabled": false, + "username": "username" + } + ])), + (status = 401, description = "Unauthorized to list all users.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to list all users.", body = ApiResponse, example = json!({"msg": "access denied"})), + (status = 500, description = "Unable return list of users.", body = ApiResponse, example = json!({"msg": "Internal error"})) + ) +)] pub async fn list_users(_role: UserAdminRole, State(appstate): State) -> ApiResult { let all_users = User::all(&appstate.pool).await?; let mut users: Vec = Vec::with_capacity(all_users.len()); @@ -102,6 +169,69 @@ pub async fn list_users(_role: UserAdminRole, State(appstate): State) }) } +/// Get user +/// +/// Return a user based on provided username parameter. +/// +/// # Returns +/// Returns `UserDetails` object or `WebError` if error occurs. +#[utoipa::path( + get, + path = "/api/v1/user/{username}", + params( + ("username" = String, description = "name of a user"), + ), + responses( + (status = 200, description = "Return details about user.", body = UserDetails, example = json!( + { + "devices": [ + { + "created": "date", + "id": 1, + "name": "name", + "networks": [ + { + "device_wireguard_ip": "1.1.1.1", + "is_active": false, + "last_connected_at": null, + "last_connected_ip": null, + "last_connected_location": null, + "network_gateway_ip": "0.0.0.0", + "network_id": 1, + "network_name": "TestNet" + } + ], + "user_id": 1, + "wireguard_pubkey": "wireguard_pubkey" + } + ], + "security_keys": [], + "user": { + "authorized_apps": [], + "email": "name@email.com", + "email_mfa_enabled": false, + "enrolled": true, + "first_name": "first_name", + "groups": [ + "group" + ], + "id": 1, + "is_active": true, + "last_name": "last_name", + "mfa_enabled": false, + "mfa_method": "None", + "phone": null, + "totp_enabled": false, + "username": "username" + }, + "wallets": [] + } + )), + (status = 401, description = "Unauthorized to return details about user.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to return details about user.", body = ApiResponse, example = json!({"msg": "access denied"})), + (status = 500, description = "Unable to return user details.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn get_user( session: SessionInfo, State(appstate): State, @@ -115,6 +245,43 @@ pub async fn get_user( }) } +/// Add user +/// +/// Add a new user based on `AddUserData` object. +/// +/// # Returns +/// Returns `UserInfo` object or `WebError` if error occurs. +#[utoipa::path( + post, + path = "/api/v1/user", + request_body = AddUserData, + responses( + (status = 201, description = "Add a new user.", body = UserInfo, example = json!( + { + "authorized_apps": [], + "email": "name@email.com", + "email_mfa_enabled": false, + "enrolled": true, + "first_name": "first_name", + "groups": [ + "admin" + ], + "id": 1, + "is_active": true, + "last_name": "last_name", + "mfa_enabled": false, + "mfa_method": "None", + "phone": null, + "totp_enabled": false, + "username": "username" + } + )), + (status = 400, description = "Bad request, invalid user data.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to create a user.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to create a user.", body = ApiResponse, example = json!({"msg": "access denied"})), + (status = 500, description = "Unable to create a user.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn add_user( _role: UserAdminRole, session: SessionInfo, @@ -132,6 +299,17 @@ pub async fn add_user( status: StatusCode::BAD_REQUEST, }); } + // check if email doesn't already exist + if User::find_by_email(&appstate.pool, &user_data.email) + .await? + .is_some() + { + debug!("User with email {} already exists", user_data.email); + return Ok(ApiResponse { + json: json!({}), + status: StatusCode::BAD_REQUEST, + }); + } let password = match &user_data.password { Some(password) => { // check password strength @@ -148,15 +326,16 @@ pub async fn add_user( }; // create new user - let mut user = User::new( + let user = User::new( user_data.username, password, user_data.last_name, user_data.first_name, user_data.email, user_data.phone, - ); - user.save(&appstate.pool).await?; + ) + .save(&appstate.pool) + .await?; if let Some(password) = user_data.password { let _result = ldap_add_user(&appstate.pool, &user, &password).await; @@ -174,7 +353,30 @@ pub async fn add_user( }) } -// Trigger enrollment process manually +/// Trigger enrollment process manually +/// +/// Allows admin to start new enrollment for user that is provided as a parameter in endpoint. +/// +/// Thanks to this endpoint you are able to trigger manually enrollment process, where after finishing you receive an enrollment token. +/// +/// `Enrollment token` allows to start the process of gaining access to the company infrastructure `(The enrollment token is valid for 24 hours)`. On the other hand, enrollment url allows the user to access the enrollment form via the web browser or perform the enrollment through the desktop client. +/// +/// Optionally this endpoint can send an email notification to the user about the enrollment. +/// # Returns +/// Returns json with `enrollment token` and `enrollment url` or `WebError` if error occurs. +#[utoipa::path( + post, + path = "/api/v1/user/{username}/start_enrollment", + request_body = StartEnrollmentRequest, + responses( + (status = 201, description = "Trigger enrollment process manually.", body = ApiResponse, example = json!({"enrollment_token": "your_enrollment_token", "enrollment_url": "your_enrollment_token"})), + (status = 400, description = "Bad request, invalid enrollment request.", body = ApiResponse, example = json!({"msg": "Email notification is enabled, but email was not provided"})), + (status = 401, description = "Unauthorized to start enrollment.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to start enrollment.", body = Json, example = json!({"msg": "access denied"})), + (status = 404, description = "Provided user does not exist.", body = Json, example = json!({"msg": "user not found"})), + (status = 500, description = "Unable to start enrollment.", body = Json, example = json!({"msg": "unexpected error"})) + ) +)] pub async fn start_enrollment( _role: UserAdminRole, session: SessionInfo, @@ -183,7 +385,7 @@ pub async fn start_enrollment( Json(data): Json, ) -> ApiResult { debug!( - "User {} starting enrollment for user {username}", + "User {} has started a new enrollment request.", session.user.username ); @@ -198,6 +400,10 @@ pub async fn start_enrollment( )); } + debug!( + "Search for the user {} in database to get started with enrollment process.", + username + ); let Some(user) = User::find_by_username(&appstate.pool, &username).await? else { error!("User {username} couldn't be found, enrollment aborted"); return Err(WebError::ObjectNotFound(format!( @@ -205,6 +411,7 @@ pub async fn start_enrollment( ))); }; + debug!("Create a new database transaction to save a new enrollment token into the database."); let mut transaction = appstate.pool.begin().await?; let config = server_config(); @@ -220,19 +427,49 @@ pub async fn start_enrollment( ) .await?; + debug!("Try to commit transaction to save the enrollment token into the databse."); transaction.commit().await?; + debug!("Transaction committed."); info!( - "User {} started enrollment for user {username}", + "The enrollment process for {} has ended with success.", session.user.username ); + debug!( + "Enrollment token {}, enrollment url {}", + enrollment_token, + config.enrollment_url.to_string() + ); Ok(ApiResponse { - json: json!({"enrollment_token": enrollment_token, "enrollment_url": config.enrollment_url.to_string()}), + json: json!({"enrollment_token": enrollment_token, "enrollment_url": config.enrollment_url.to_string()}), status: StatusCode::CREATED, }) } +/// Start remote desktop configuration +/// +/// Allows admin to start new remote desktop configuration for user that is provided as a parameter in endpoint. +/// +/// Thanks to this endpoint you are able to receive a new desktop client configuration or update an existing one. Users need the configuration to connect to the company infrastrcture. +/// +/// `Enrollment token` allows to start the process of gaining access to the company infrastructure `(The enrollment token is valid for 24 hours)`. On the other hand, enrollment url allows the user to access the enrollment form via the web browser or perform the enrollment through the desktop client. +/// +/// Optionally this endpoint can send an email notification to the user about the enrollment.``` +/// # Returns +/// Returns json with `enrollment token` and `enrollment url` or `WebError` if error occurs. +#[utoipa::path( + post, + path = "/api/v1/user/{username}/start_desktop", + request_body = StartEnrollmentRequest, + responses( + (status = 201, description = "Trigger enrollment process manually.", body = Json, example = json!({"enrollment_token": "your_enrollment_token", "enrollment_url": "your_enrollment_token"})), + (status = 400, description = "Bad request, invalid enrollment request.", body = Json, example = json!({"msg": "Email notification is enabled, but email was not provided"})), + (status = 401, description = "Unauthorized to start remote desktop configuration.", body = Json, example = json!({"msg": "Can't create desktop configuration enrollment token for disabled user "})), + (status = 404, description = "Provided user does not exist.", body = Json, example = json!({"msg": "user not found"})), + (status = 500, description = "Unable to start remote desktop configuration.", body = Json, example = json!({"msg": "unexpected error"})) + ) +)] pub async fn start_remote_desktop_configuration( session: SessionInfo, State(appstate): State, @@ -240,11 +477,13 @@ pub async fn start_remote_desktop_configuration( Json(data): Json, ) -> ApiResult { debug!( - "User {} starting enrollment for user {username}", + "User {} has started a new desktop activation for {username}.", session.user.username ); + debug!("Verify that the user from the current session is an admin or only peforms desktop activation for self."); let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; + debug!("Successfully fetched user data: {user:?}"); // if email is None assume that email should be sent to enrolling user let email = match data.email { @@ -252,10 +491,15 @@ pub async fn start_remote_desktop_configuration( None => user.email.clone(), }; + debug!("Create a new database transaction to save a desktop configuration token into the database."); let mut transaction = appstate.pool.begin().await?; + debug!( + "Generating a new desktop activation token by {}.", + session.user.username + ); let config = server_config(); - let enrollment_token = user + let desktop_configuration_token = user .start_remote_desktop_configuration( &mut transaction, &session.user, @@ -267,19 +511,47 @@ pub async fn start_remote_desktop_configuration( ) .await?; + debug!("Try to submit transaction to save the desktop configuration token into the databse."); transaction.commit().await?; + debug!("Transaction submitted."); info!( - "User {} started enrollment for user {username}", + "User {} started a new desktop activation.", session.user.username ); + debug!( + "Desktop configuration token {}, desktop configuration url {}", + desktop_configuration_token, + config.enrollment_url.to_string() + ); Ok(ApiResponse { - json: json!({"enrollment_token": enrollment_token, "enrollment_url": config.enrollment_url.to_string()}), + json: json!({"enrollment_token": desktop_configuration_token, "enrollment_url": config.enrollment_url.to_string()}), status: StatusCode::CREATED, }) } +/// Verify if the user is available +/// +/// Check if user is available by provided `Username` object. +/// Username is unique so database returns only single user or nothing. +/// +/// # Returns +/// Returns only status code 200 if user is available or `WebError` if error occurs. +/// +/// `Please take notice that if user exists in database, endpoint will return status code 400.` +#[utoipa::path( + post, + path = "/api/v1/user/available", + request_body = Json, + responses( + (status = 200, description = "Provided username is available to use.", body = ApiResponse, example = json!({})), + (status = 400, description = "Bad request, provided username is not available or username is invalid.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to check is username available.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to check is username available.", body = ApiResponse, example = json!({"msg": "access denied"})), + (status = 500, description = "Unable to check is username available.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn username_available( _role: UserAdminRole, State(appstate): State, @@ -305,6 +577,27 @@ pub async fn username_available( }) } +/// Modify user +/// +/// Update users data, it can remove authorized apps and active/deactivate ldap status if needed. +/// Endpoint is able to disable a user, but `admin cannot disable himself`. +/// +/// # Returns +/// If erorr occurs, endpoint will return `WebError` object. +#[utoipa::path( + put, + path = "/api/v1/user/{username}", + params( + ("username" = String, description = "name of a user"), + ), + request_body = Json, + responses( + (status = 200, description = "User has been updated."), + (status = 400, description = "Bad request, unable to change user data. Verify user data that you want to update.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to modify user.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 500, description = "Unable to modify user.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn modify_user( session: SessionInfo, State(appstate): State, @@ -358,7 +651,7 @@ pub async fn modify_user( .await? { debug!( - "User {} changed {username} groups or status, syncing allowed network devices", + "User {} changed {username} groups or status, syncing allowed network devices.", session.user.username ); let networks = WireguardNetwork::all(&mut *transaction).await?; @@ -385,6 +678,27 @@ pub async fn modify_user( Ok(ApiResponse::default()) } +/// Delete user +/// +/// Endpoint helps you delete a user, but `you can't delete yourself as a administrator`. +/// +/// # Returns +/// If erorr occurs, endpoint will return `WebError` object. +#[utoipa::path( + delete, + path = "/api/v1/user/{username}", + params( + ("username" = String, description = "name of a user"), + ), + responses( + (status = 200, description = "User has been deleted."), + (status = 400, description = "Bad request, unable to delete user.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to delete user.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to delete user.", body = ApiResponse, example = json!({"msg": "access denied"})), + (status = 404, description = "User does not exist with username: ", body = ApiResponse, example = json!({"msg": "User not found"})), + (status = 500, description = "Unable to delete user.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn delete_user( _role: UserAdminRole, State(appstate): State, @@ -400,9 +714,27 @@ pub async fn delete_user( }); } if let Some(user) = User::find_by_username(&appstate.pool, &username).await? { - user.delete(&appstate.pool).await?; - let _result = ldap_delete_user(&appstate.pool, &username).await; + // Get rid of all devices of the deleted user from networks first + debug!( + "User {} deleted user {username}, purging their network devices across all networks.", + session.user.username + ); + let mut transaction = appstate.pool.begin().await?; + let devices = user.devices(&mut *transaction).await?; + let mut events = Vec::new(); + for device in devices { + events.push(GatewayEvent::DeviceDeleted( + DeviceInfo::from_device(&mut *transaction, device).await?, + )); + } + appstate.send_multiple_wireguard_events(events); + debug!("Devices of user {username} purged from networks."); + + user.delete(&mut *transaction).await?; + let _result = ldap_delete_user(&mut *transaction, &username).await; appstate.trigger_action(AppEvent::UserDeleted(username.clone())); + transaction.commit().await?; + info!("User {} deleted user {}", session.user.username, &username); Ok(ApiResponse::default()) } else { @@ -413,6 +745,23 @@ pub async fn delete_user( } } +/// Change your own password +/// +/// Change your own password, it could return error if password is not strong enough. +/// +/// # Returns +/// If erorr occurs, endpoint will return `WebError` object. +#[utoipa::path( + put, + path = "/api/v1/user/change_password", + request_body = Json, + responses( + (status = 200, description = "Pasword has been changed.", body = ApiResponse, example = json!({})), + (status = 400, description = "Bad request, provided passwords are not same or new password does not satisfy requirements.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to change password.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 500, description = "Unable to change your password", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn change_self_password( session: SessionInfo, State(appstate): State, @@ -448,6 +797,30 @@ pub async fn change_self_password( }) } +/// Change user password +/// +/// Change user password, it could return error if password is not strong enough. +/// +/// `This endpoint doesn't allow you to change your own password. Go to: /api/v1/user/change_password.` +/// +/// # Returns +/// If erorr occurs, endpoint will return `WebError` object. +#[utoipa::path( + put, + path = "/api/v1/user/{username}/password", + params( + ("username" = String, description = "name of a user"), + ), + request_body = Json, + responses( + (status = 200, description = "Pasword has been changed.", body = ApiResponse, example = json!({})), + (status = 400, description = "Bad request, password does not satisfy requirements. This endpoint does not change your own password.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to change password.", body = Json, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to change user password.", body = ApiResponse, example = json!({"msg": "access denied"})), + (status = 404, description = "Cannot change user password that does not exist.", body = ApiResponse, example = json!({})), + (status = 500, description = "Unable to change user password", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn change_password( _role: UserAdminRole, session: SessionInfo, @@ -503,6 +876,29 @@ pub async fn change_password( } } +/// Reset user password +/// +/// Reset user password, it will send a new enrollment to the user's email. +/// +/// `This endpoint doesn't allow you to reset your own password.` +/// +/// # Returns +/// If erorr occurs, endpoint will return `WebError` object. +#[utoipa::path( + post, + path = "/api/v1/user/{username}/reset_password", + params( + ("username" = String, description = "name of a user"), + ), + responses( + (status = 200, description = "Successfully reset user password."), + (status = 400, description = "Bad request, this endpoint does not change your own password.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to change password.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to change user password.", body = ApiResponse, example = json!({"msg": "access denied"})), + (status = 404, description = "Cannot reset user password that does not exist.", body = ApiResponse, example = json!({})), + (status = 500, description = "Unable to send reset password to email", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn reset_password( _role: UserAdminRole, session: SessionInfo, @@ -527,16 +923,12 @@ pub async fn reset_password( if let Some(user) = user { let mut transaction = appstate.pool.begin().await?; - Token::delete_unused_user_password_reset_tokens( - &mut transaction, - user.id.expect("Missing user ID"), - ) - .await?; + Token::delete_unused_user_password_reset_tokens(&mut transaction, user.id).await?; let config = server_config(); let enrollment = Token::new( - user.id.expect("Missing user ID"), - Some(session.user.id.expect("Missing admin ID")), + user.id, + Some(session.user.id), Some(user.email.clone()), config.password_reset_token_timeout.as_secs(), Some(PASSWORD_RESET_TOKEN_TYPE.to_string()), @@ -590,13 +982,37 @@ pub async fn reset_password( } /// Similar to [`models::WalletInfo`] but without `use_for_mfa`. -#[derive(Deserialize)] +#[derive(Deserialize, ToSchema)] pub struct WalletInfoShort { pub address: String, pub name: String, pub chain_id: i64, } +/// Wallet challenge +/// +/// Endpoint allows to generate a wallet challenge for ownership verification. +/// +/// # Returns +/// Returns `WalletChallenge` object or `WebError` object if error occurs. +#[utoipa::path( + get, + path = "/api/v1/user/{username}/challenge", + params( + ("username" = String, description = "name of a user"), + ), + responses( + (status = 200, description = "Return successfully wallet challenge details.", body = WalletChallenge, example = json!( + { + "id": 1, + "message": "message" + } + )), + (status = 401, description = "Unauthorized to return wallet challenge.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 404, description = "Wrong address or wallet challenge is alredy validated.", body = ApiResponse, example = json!({"msg": "wrong address"})), + (status = 500, description = "Cannot retrive settings.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn wallet_challenge( session: SessionInfo, State(appstate): State, @@ -611,49 +1027,68 @@ pub async fn wallet_challenge( // check if address already exists let wallet = if let Some(wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id.unwrap(), &wallet_info.address) - .await? + Wallet::find_by_user_and_address(&appstate.pool, user.id, &wallet_info.address).await? { if wallet.validation_timestamp.is_some() { error!( - "Can't generate wallet challange for user {username}, the wallet {} is already validated", + "Can't generate wallet challange for user {username}, the wallet {} is already validated", wallet_info.address ); return Err(WebError::ObjectNotFound("wrong address".into())); } wallet } else { - let challenge_message = - if let Some(settings) = Settings::find_by_id(&appstate.pool, 1).await? { - Wallet::format_challenge(&wallet_info.address, &settings.challenge_template) - } else { - error!("Cannot retrieve settings"); - return Err(WebError::DbError("cannot retrieve settings".into())); - }; - let mut wallet = Wallet::new_for_user( - user.id.unwrap(), + let challenge_message = if let Some(settings) = Settings::get(&appstate.pool).await? { + Wallet::format_challenge(&wallet_info.address, &settings.challenge_template) + } else { + error!("Cannot retrieve settings"); + return Err(WebError::DbError("cannot retrieve settings".into())); + }; + Wallet::new_for_user( + user.id, wallet_info.address, wallet_info.name, wallet_info.chain_id, challenge_message, - ); - wallet.save(&appstate.pool).await?; - wallet + ) + .save(&appstate.pool) + .await? }; info!( "User {} generated wallet challenge for user {username}", session.user.username ); + Ok(ApiResponse { json: json!(WalletChallenge { - id: wallet.id.unwrap(), + id: wallet.id, message: wallet.challenge_message }), status: StatusCode::OK, }) } +/// Set wallet +/// +/// Set a new signature to user wallet by providing `WalletSignature` object. +/// +/// # Returns +/// It returns `WebError` object if error occurs. +#[utoipa::path( + get, + path = "/api/v1/user/{username}/wallet", + params( + ("username" = String, description = "name of a user"), + ), + responses( + (status = 200, description = "Successfully set wallet signature."), + (status = 401, description = "Unauthorized to set a new signature.", body = Json, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to set new signature to wallet.", body = Json, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Incorrect wallet signature or address, can't set new signature for user.", body = ApiResponse, example = json!({"msg": "wallet not found"})), + (status = 500, description = "Cannot set a new wallet signature", body = Json, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn set_wallet( session: SessionInfo, State(appstate): State, @@ -666,8 +1101,7 @@ pub async fn set_wallet( ); let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(mut wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id.unwrap(), &wallet_info.address) - .await? + Wallet::find_by_user_and_address(&appstate.pool, user.id, &wallet_info.address).await? { if wallet.validate_signature(&wallet_info.signature).is_ok() { wallet @@ -695,7 +1129,28 @@ pub async fn set_wallet( } /// Change wallet. +/// +/// Updates user wallet. /// Currently only `use_for_mfa` flag can be set or unset. +/// +/// # Returns +/// Returns `RecoveryCodes` object or `WebError` object if error occurs. +#[utoipa::path( + put, + path = "/api/v1/user/{username}/wallet/{address}", + params( + ("username" = String, description = "name of a user"), + ("address" = String, description = "address of a user portfel") + ), + request_body = Json, + responses( + (status = 200, description = "Successfully updated user's wallet.", body = RecoveryCodes, example = json!({"codes": "[]"})), + (status = 401, description = "Unauthorized to udpate user wallet.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to update user wallet.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Incorrect wallet, can't update user wallet.", body = ApiResponse, example = json!({"msg": "wallet not found"})), + (status = 500, description = "Cannot udpate user wallet.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn update_wallet( session: SessionInfo, Path((username, address)): Path<(String, String)>, @@ -708,9 +1163,9 @@ pub async fn update_wallet( ); let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(mut wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id.unwrap(), &address).await? + Wallet::find_by_user_and_address(&appstate.pool, user.id, &address).await? { - if Some(wallet.user_id) == user.id { + if wallet.user_id == user.id { let mfa_change = wallet.use_for_mfa != data.use_for_mfa; wallet.use_for_mfa = data.use_for_mfa; wallet.save(&appstate.pool).await?; @@ -749,7 +1204,7 @@ pub async fn update_wallet( Ok(ApiResponse::default()) } else { error!( - "User {} failed to update wallet {address} for user {username} (id: {:?}), the owner id is {}", + "User {} failed to update wallet {address} for user {username} (id: {}), the owner id is {}", session.user.username, user.id, wallet.user_id ); Err(WebError::ObjectNotFound("wrong wallet".into())) @@ -764,6 +1219,26 @@ pub async fn update_wallet( } /// Delete wallet. +/// +/// Endpoint helps you to delete user wallet. +/// +/// # Returns +/// Returns `WebError` object if error occurs. +#[utoipa::path( + delete, + path = "/api/v1/user/{username}/wallet/{address}", + params( + ("username" = String, description = "name of a user"), + ("address" = String, description = "address of a user portfel") + ), + responses( + (status = 200, description = "Successfully deleted user's wallet."), + (status = 401, description = "Unauthorized to delete user wallet.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to delete user wallet.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Incorrect wallet, can't delete user wallet.", body = ApiResponse, example = json!({"msg": "wallet not found"})), + (status = 500, description = "Cannot delete user wallet.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn delete_wallet( session: SessionInfo, State(appstate): State, @@ -775,9 +1250,9 @@ pub async fn delete_wallet( ); let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(wallet) = - Wallet::find_by_user_and_address(&appstate.pool, user.id.unwrap(), &address).await? + Wallet::find_by_user_and_address(&appstate.pool, user.id, &address).await? { - if Some(wallet.user_id) == user.id { + if wallet.user_id == user.id { wallet.delete(&appstate.pool).await?; user.verify_mfa_state(&appstate.pool).await?; info!( @@ -801,6 +1276,27 @@ pub async fn delete_wallet( } } +/// Delete security key +/// +/// Delete Webauthn security key that allows users to authenticate. +/// +/// # Returns +/// Returns `WebError` object if error occurs. +#[utoipa::path( + delete, + path = "/api/v1/user/{username}/security_key/{id}", + params( + ("username" = String, description = "name of a user"), + ("id" = i64, description = "id of security key that could point to passkey") + ), + responses( + (status = 200, description = "Successfully deleted security key."), + (status = 401, description = "Unauthorized to delete security key.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to delete security key.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Incorrect authorized app, not found.", body = ApiResponse, example = json!({"msg": "security key not found"})), + (status = 500, description = "Cannot delete authorized app.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn delete_security_key( session: SessionInfo, State(appstate): State, @@ -812,7 +1308,7 @@ pub async fn delete_security_key( ); let mut user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(webauthn) = WebAuthn::find_by_id(&appstate.pool, id).await? { - if Some(webauthn.user_id) == user.id { + if webauthn.user_id == user.id { webauthn.delete(&appstate.pool).await?; user.verify_mfa_state(&appstate.pool).await?; info!( @@ -836,6 +1332,40 @@ pub async fn delete_security_key( } } +/// Returns your data +/// +/// Endpoint returns the data associated with the current session user``` +/// +/// # Returns +/// Returns `UserInfo` object or `WebError` object if error occurs. +#[utoipa::path( + get, + path = "/api/v1/me", + responses( + (status = 200, description = "Returns your own data.", body = UserInfo, example = json!( + { + "authorized_apps": [], + "email": "name@email.com", + "email_mfa_enabled": false, + "enrolled": true, + "first_name": "first_name", + "groups": [ + "group" + ], + "id": 1, + "is_active": true, + "last_name": "last_name", + "mfa_enabled": false, + "mfa_method": "None", + "phone": null, + "totp_enabled": false, + "username": "username" + } + )), + (status = 401, description = "Unauthorized return own user data.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 500, description = "Cannot retrive own user data.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn me(session: SessionInfo, State(appstate): State) -> ApiResult { let user_info = UserInfo::from_user(&appstate.pool, &session.user).await?; Ok(ApiResponse { @@ -845,6 +1375,26 @@ pub async fn me(session: SessionInfo, State(appstate): State) -> ApiRe } /// Delete Oauth token. +/// +/// Endpoint helps your to delete authorized application by `OAuth2` id. +/// +/// # Returns +/// Returns `WebError` object if error occurs. +#[utoipa::path( + delete, + path = "/api/v1/user/{username}/oauth_app/{oauth2client_id}", + params( + ("username" = String, description = "name of a user"), + ("oauth2client_id" = i64, description = "id of OAuth2 client") + ), + responses( + (status = 200, description = "Successfully deleted authorized app."), + (status = 401, description = "Unauthorized to delete authorized app.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to delete authorized app.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 404, description = "Incorrect authorized app, not found.", body = ApiResponse, example = json!({"msg": "Authorized app not found"})), + (status = 500, description = "Cannot delete authorized app.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn delete_authorized_app( session: SessionInfo, State(appstate): State, @@ -857,12 +1407,12 @@ pub async fn delete_authorized_app( let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; if let Some(app) = OAuth2AuthorizedApp::find_by_user_and_oauth2client_id( &appstate.pool, - user.id.unwrap(), + user.id, oauth2client_id, ) .await? { - if Some(app.user_id) == user.id { + if app.user_id == user.id { app.delete(&appstate.pool).await?; info!( "User {} deleted OAuth2 client {oauth2client_id} for user {username}", @@ -887,9 +1437,27 @@ pub async fn delete_authorized_app( #[cfg(test)] mod test { - use super::*; use claims::{assert_err, assert_ok}; + use super::*; + + #[test] + fn test_username_prune() { + assert_eq!(prune_username("zenek"), "zenek"); + assert_eq!(prune_username("zenek34"), "zenek34"); + assert_eq!(prune_username("zenek@34"), "zenek34"); + assert_eq!(prune_username("first.last"), "first.last"); + assert_eq!(prune_username("__zenek__"), "zenek__"); + assert_eq!(prune_username("zenek?"), "zenek"); + assert_eq!(prune_username("zenek!"), "zenek"); + assert_eq!( + prune_username( + "averylongnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + ), + "averylongnameeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" + ); + } + #[test] fn test_username_validation() { // valid usernames diff --git a/src/handlers/webhooks.rs b/src/handlers/webhooks.rs index eb321307f8..cc6513e36c 100644 --- a/src/handlers/webhooks.rs +++ b/src/handlers/webhooks.rs @@ -4,7 +4,7 @@ use axum::{ }; use serde_json::json; -use super::{ApiResponse, ApiResult}; +use super::{ApiResponse, ApiResult, WebHookData}; use crate::{ appstate::AppState, auth::{AdminRole, SessionInfo}, @@ -15,15 +15,17 @@ pub async fn add_webhook( _admin: AdminRole, session: SessionInfo, State(appstate): State, - Json(mut webhook): Json, + Json(webhookdata): Json, ) -> ApiResult { - let url = webhook.url.clone(); + let url = webhookdata.url.clone(); debug!("User {} adding webhook {url}", session.user.username); + let webhook: WebHook = webhookdata.into(); let status = match webhook.save(&appstate.pool).await { - Ok(()) => StatusCode::CREATED, + Ok(_) => StatusCode::CREATED, Err(_) => StatusCode::BAD_REQUEST, }; info!("User {} added webhook {url}", session.user.username); + Ok(ApiResponse { json: json!({}), status, @@ -33,6 +35,7 @@ pub async fn add_webhook( // TODO: paginate pub async fn list_webhooks(_admin: AdminRole, State(appstate): State) -> ApiResult { let webhooks = WebHook::all(&appstate.pool).await?; + Ok(ApiResponse { json: json!(webhooks), status: StatusCode::OK, @@ -56,18 +59,6 @@ pub async fn get_webhook( } } -#[derive(Deserialize, Serialize)] -pub struct WebHookData { - pub url: String, - pub description: String, - pub token: String, - pub enabled: bool, - pub on_user_created: bool, - pub on_user_deleted: bool, - pub on_user_modified: bool, - pub on_hwkey_provision: bool, -} - pub async fn change_webhook( _admin: AdminRole, session: SessionInfo, @@ -92,6 +83,7 @@ pub async fn change_webhook( None => StatusCode::NOT_FOUND, }; info!("User {} updated webhook {id}", session.user.username); + Ok(ApiResponse { json: json!({}), status, diff --git a/src/handlers/wireguard.rs b/src/handlers/wireguard.rs index f860209374..149433e2d6 100644 --- a/src/handlers/wireguard.rs +++ b/src/handlers/wireguard.rs @@ -12,6 +12,8 @@ use axum::{ use chrono::{DateTime, Duration, NaiveDateTime, Utc}; use ipnetwork::IpNetwork; use serde_json::{json, Value}; +use sqlx::PgPool; +use utoipa::ToSchema; use uuid::Uuid; use super::{device_for_admin_or_self, user_for_admin_or_self, ApiResponse, ApiResult, WebError}; @@ -25,8 +27,9 @@ use crate::{ }, wireguard::{DateTimeAggregation, MappedDevice, WireguardNetworkInfo}, }, - AddDevice, DbPool, Device, GatewayEvent, WireguardNetwork, + AddDevice, Device, GatewayEvent, Id, WireguardNetwork, }, + enterprise::handlers::CanManageDevices, grpc::GatewayMap, handlers::mail::send_new_device_added_email, server_config, @@ -34,7 +37,7 @@ use crate::{ wg_config::{parse_wireguard_config, ImportedDevice}, }; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, ToSchema)] pub struct WireguardNetworkData { pub name: String, pub address: IpNetwork, @@ -74,10 +77,21 @@ pub struct ImportNetworkData { #[derive(Serialize, Deserialize)] pub struct ImportedNetworkData { - pub network: WireguardNetwork, + pub network: WireguardNetwork, pub devices: Vec, } +// #[utoipa::path( +// get, +// path = "/api/v1/network", +// request_body = WireguardNetworkData, +// responses( +// (status = 201, description = "Successfully created network.", body = WireguardNetwork), +// (status = 401, description = "Unauthorized to create network.", body = Json, example = json!({"msg": "Session is required"})), +// (status = 403, description = "You don't have permission to return details about user.", body = Json, body = Json, example = json!({"msg": "access denied"})), +// (status = 500, description = "Unable to create network.", body = Json, example = json!({"msg": "Invalid network address"})) +// ) +// )] pub async fn create_network( _role: VpnRole, State(appstate): State, @@ -90,7 +104,7 @@ pub async fn create_network( session.user.username ); let allowed_ips = data.parse_allowed_ips(); - let mut network = WireguardNetwork::new( + let network = WireguardNetwork::new( data.name, data.address, data.port, @@ -104,7 +118,7 @@ pub async fn create_network( .map_err(|_| WebError::Serialization("Invalid network address".into()))?; let mut transaction = appstate.pool.begin().await?; - network.save(&mut *transaction).await?; + let network = network.save(&mut *transaction).await?; network .set_allowed_groups(&mut transaction, data.allowed_groups) .await?; @@ -113,19 +127,7 @@ pub async fn create_network( network.add_all_allowed_devices(&mut transaction).await?; info!("Assigning IPs for existing devices in network {network}"); - match &network.id { - Some(network_id) => { - appstate - .send_wireguard_event(GatewayEvent::NetworkCreated(*network_id, network.clone())); - } - None => { - error!("Network {} ID was not created during network creation, gateway event was not sent!", network.name); - return Ok(ApiResponse { - json: json!({}), - status: StatusCode::INTERNAL_SERVER_ERROR, - }); - } - } + appstate.send_wireguard_event(GatewayEvent::NetworkCreated(network.id, network.clone())); transaction.commit().await?; @@ -133,13 +135,14 @@ pub async fn create_network( "User {} created WireGuard network {network_name}", session.user.username ); + Ok(ApiResponse { json: json!(network), status: StatusCode::CREATED, }) } -async fn find_network(id: i64, pool: &DbPool) -> Result { +async fn find_network(id: Id, pool: &PgPool) -> Result, WebError> { WireguardNetwork::find_by_id(pool, id) .await? .ok_or_else(|| WebError::ObjectNotFound(format!("Network {id} not found"))) @@ -177,22 +180,12 @@ pub async fn modify_network( .await?; let _events = network.sync_allowed_devices(&mut transaction, None).await?; - match &network.id { - Some(network_id) => { - let peers = network.get_peers(&mut *transaction).await?; - appstate.send_wireguard_event(GatewayEvent::NetworkModified( - *network_id, - network.clone(), - peers, - )); - } - &None => { - error!( - "Network {} id not found, gateway update not sent!", - network.name - ); - } - } + let peers = network.get_peers(&mut *transaction).await?; + appstate.send_wireguard_event(GatewayEvent::NetworkModified( + network.id, + network.clone(), + peers, + )); // commit DB transaction transaction.commit().await?; @@ -225,6 +218,7 @@ pub async fn delete_network( "User {} deleted WireGuard network {network_id}", session.user.username, ); + Ok(ApiResponse::default()) } @@ -238,7 +232,7 @@ pub async fn list_networks( let networks = WireguardNetwork::all(&appstate.pool).await?; for network in networks { - let network_id = network.id.expect("Network does not have an ID"); + let network_id = network.id; let allowed_groups = network.fetch_allowed_groups(&appstate.pool).await?; { let gateway_state = gateway_state @@ -351,21 +345,13 @@ pub async fn import_network( network.endpoint = data.endpoint; let mut transaction = appstate.pool.begin().await?; - network.save(&mut *transaction).await?; + let network = network.save(&mut *transaction).await?; network .set_allowed_groups(&mut transaction, data.allowed_groups) .await?; info!("New network {network} created"); - match network.id { - Some(network_id) => { - appstate - .send_wireguard_event(GatewayEvent::NetworkCreated(network_id, network.clone())); - } - None => { - error!("Network {network} id not found, gateway event not sent!"); - } - } + appstate.send_wireguard_event(GatewayEvent::NetworkCreated(network.id, network.clone())); let reserved_ips: Vec = imported_devices .iter() @@ -446,7 +432,66 @@ pub async fn add_user_devices( } } +// assign IPs and generate configs for each network +#[derive(Serialize, ToSchema)] +pub struct AddDeviceResult { + configs: Vec, + device: Device, +} + +/// Add device +/// +/// Add a new device for a user by sending `AddDevice` object. +/// Notice that `wireguard_pubkey` must be unique to successfully add the device. +/// You can't add devices for `disabled` users, unless you are an admin. +/// +/// Device will be added to all networks in your company infrastructure. +/// +/// User will receive all new device details on email. +/// +/// # Returns +/// Returns `AddDeviceResult` object or `WebError` object if error occurs. +#[utoipa::path( + post, + path = "/api/v1/device/{device_id}", + params( + ("device_id" = String, description = "Name of a user.") + ), + request_body = AddDevice, + responses( + (status = 201, description = "Successfully added a new device for a user.", body = AddDeviceResult, example = json!( + { + "configs": [ + { + "network_id": 0, + "network_name": "network_name", + "config": "config", + "address": "0.0.0.0:8000", + "endpoint": "endpoint", + "allowed_ips": ["0.0.0.0:8000"], + "pubkey": "pubkey", + "dns": "8.8.8.8", + "mfa_enabled": false, + "keepalive_interval": 5 + } + ], + "device": { + "id": 0, + "name": "name", + "wireguard_pubkey": "wireguard_pubkey", + "user_id": 0, + "created": "2024-07-10T10:25:43.231Z" + } + } + )), + (status = 400, description = "Bad request, no networks found or device with pubkey that you want to send with already exists.", body = ApiResponse, example = json!({})), + (status = 401, description = "Unauthorized to add a new device for a user.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to add a new device for a user. You can't add a new device for a disabled user.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + (status = 500, description = "Cannot add a new device for a user.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn add_device( + _can_manage_devices: CanManageDevices, session: SessionInfo, State(appstate): State, // Alias, because otherwise `axum` reports conflicting routes. @@ -494,24 +539,10 @@ pub async fn add_device( } // save device - let Some(user_id) = user.id else { - error!( - "Failed to add device {device_name}, user {} has no id", - user.username - ); - return Err(WebError::ModelError("User has no id".to_string())); - }; - let mut device = Device::new(add_device.name, add_device.wireguard_pubkey, user_id); - let mut transaction = appstate.pool.begin().await?; - device.save(&mut *transaction).await?; - - // assign IPs and generate configs for each network - #[derive(Serialize)] - struct AddDeviceResult { - configs: Vec, - device: Device, - } + let device = Device::new(add_device.name, add_device.wireguard_pubkey, user.id) + .save(&mut *transaction) + .await?; let (network_info, configs) = device.add_to_all_networks(&mut transaction).await?; @@ -567,7 +598,40 @@ pub async fn add_device( }) } +/// Modify device +/// +/// Update a device for a user by sending `ModifyDevice` object. +/// Notice that `wireguard_pubkey` must be diffrent from server's pubkey. +/// +/// Endpoint will trigger new update in gateway server. +/// +/// # Returns +/// Returns `Device` object or `WebError` object if error occurs. +#[utoipa::path( + put, + path = "/api/v1/device/{device_id}", + params( + ("device_id" = i64, description = "Id of device to update details.") + ), + request_body = ModifyDevice, + responses( + (status = 200, description = "Successfully updated a device.", body = Device, example = json!( + { + "id": 0, + "name": "name", + "wireguard_pubkey": "wireguard_pubkey", + "user_id": 0, + "created": "2024-07-10T10:25:43.231Z" + } + )), + (status = 400, description = "Bad request, no networks found or device with pubkey that you want to send with is a server's pubkey.", body = ApiResponse, example = json!({"msg": "device's pubkey must be different from server's pubkey"})), + (status = 401, description = "Unauthorized to update a device.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 404, description = "Device not found.", body = ApiResponse, example = json!({"msg": "device id not found"})), + (status = 500, description = "Cannot update a device.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn modify_device( + _can_manage_devices: CanManageDevices, session: SessionInfo, Path(device_id): Path, State(appstate): State, @@ -603,20 +667,16 @@ pub async fn modify_device( // send update to gateway's let mut network_info = Vec::new(); for network in &networks { - if let Some(network_id) = network.id { - if let Some(device_id) = device.id { - let wireguard_network_device = - WireguardNetworkDevice::find(&appstate.pool, device_id, network_id).await?; - if let Some(wireguard_network_device) = wireguard_network_device { - let device_network_info = DeviceNetworkInfo { - network_id, - device_wireguard_ip: wireguard_network_device.wireguard_ip, - preshared_key: wireguard_network_device.preshared_key, - is_authorized: wireguard_network_device.is_authorized, - }; - network_info.push(device_network_info); - } - } + let wireguard_network_device = + WireguardNetworkDevice::find(&appstate.pool, device.id, network.id).await?; + if let Some(wireguard_network_device) = wireguard_network_device { + let device_network_info = DeviceNetworkInfo { + network_id: network.id, + device_wireguard_ip: wireguard_network_device.wireguard_ip, + preshared_key: wireguard_network_device.preshared_key, + is_authorized: wireguard_network_device.is_authorized, + }; + network_info.push(device_network_info); } } appstate.send_wireguard_event(GatewayEvent::DeviceModified(DeviceInfo { @@ -631,6 +691,31 @@ pub async fn modify_device( }) } +/// Get device +/// +/// # Returns +/// Returns `Device` object or `WebError` object if error occurs. +#[utoipa::path( + get, + path = "/api/v1/device/{device_id}", + params( + ("device_id" = i64, description = "Id of device to update details.") + ), + responses( + (status = 200, description = "Successfully updated a device.", body = Device, example = json!( + { + "id": 0, + "name": "name", + "wireguard_pubkey": "wireguard_pubkey", + "user_id": 0, + "created": "2024-07-10T10:25:43.231Z" + } + )), + (status = 400, description = "Bad request, no networks found or device with pubkey that you want to send with is a server's pubkey.", body = ApiResponse, example = json!({"msg": "device's pubkey must be different from server's pubkey"})), + (status = 401, description = "Unauthorized to update a device.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 404, description = "Device not found.", body = ApiResponse, example = json!({"msg": "device id not found"})) + ) +)] pub async fn get_device( session: SessionInfo, Path(device_id): Path, @@ -645,7 +730,27 @@ pub async fn get_device( }) } +/// Delete device +/// +/// Delete user device and trigger new update in gateway server. +/// +/// # Returns +/// If error occurs it returns `WebError` object. +#[utoipa::path( + delete, + path = "/api/v1/device/{device_id}", + params( + ("device_id" = i64, description = "Id of device to update details.") + ), + responses( + (status = 200, description = "Successfully deleted device."), + (status = 401, description = "Unauthorized to update a device.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 404, description = "Device not found.", body = ApiResponse, example = json!({"msg": "device id not found"})), + (status = 500, description = "Cannot update a device.", body = ApiResponse, example = json!({"msg": "Internal server error"})) + ) +)] pub async fn delete_device( + _can_manage_devices: CanManageDevices, session: SessionInfo, Path(device_id): Path, State(appstate): State, @@ -660,6 +765,27 @@ pub async fn delete_device( Ok(ApiResponse::default()) } +/// List all devices +/// +/// # Returns +/// Returns a list `Device` objects or `WebError` object if error occurs. +#[utoipa::path( + get, + path = "/api/v1/device", + responses( + (status = 200, description = "List all devices.", body = [Device], example = json!([ + { + "id": 0, + "name": "name", + "wireguard_pubkey": "wireguard_pubkey", + "user_id": 0, + "created": "2024-07-10T10:25:43.231Z" + } + ])), + (status = 401, description = "Unauthorized to list all devices.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to list all devices.", body = ApiResponse, example = json!({"msg": "requires privileged access"})), + ) +)] pub async fn list_devices(_role: VpnRole, State(appstate): State) -> ApiResult { debug!("Listing devices"); let devices = Device::all(&appstate.pool).await?; @@ -671,6 +797,32 @@ pub async fn list_devices(_role: VpnRole, State(appstate): State) -> A }) } +/// List user devices +/// +/// This endpoint requires `admin` role. +/// +/// # Returns +/// Returns a list of `Device` object or `WebError` object if error occurs. +#[utoipa::path( + get, + path = "/api/v1/device/user/{username}", + params( + ("username" = String, description = "Name of a user.") + ), + responses( + (status = 200, description = "List user devices.", body = [Device], example = json!([ + { + "id": 0, + "name": "name", + "wireguard_pubkey": "wireguard_pubkey", + "user_id": 0, + "created": "2024-07-10T10:25:43.231Z" + } + ])), + (status = 401, description = "Unauthorized to list user devices.", body = ApiResponse, example = json!({"msg": "Session is required"})), + (status = 403, description = "You don't have permission to list user devices.", body = ApiResponse, example = json!({"msg": "Admin access required"})), + ) +)] pub async fn list_user_devices( session: SessionInfo, State(appstate): State, @@ -708,18 +860,13 @@ pub async fn download_config( info!("Created config for device {}({device_id})", device.name); Ok(device.create_config(&network, &wireguard_network_device)) } else { - let device_id = if let Some(id) = device.id { - id.to_string() - } else { - String::new() - }; error!( - "Failed to create config, no IP address found for device: {}({device_id})", - device.name + "Failed to create config, no IP address found for device: {}({})", + device.name, device.id ); Err(WebError::ObjectNotFound(format!( - "No IP address found for device: {}({device_id})", - device.name + "No IP address found for device: {}({})", + device.name, device.id ))) } } diff --git a/src/handlers/yubikey.rs b/src/handlers/yubikey.rs index aab077d88c..c8660f7408 100644 --- a/src/handlers/yubikey.rs +++ b/src/handlers/yubikey.rs @@ -15,22 +15,19 @@ pub async fn delete_yubikey( ) -> ApiResult { debug!("Deleting yubikey {key_id} by {:?}", &session.user.id); let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let user_id = user - .id - .ok_or(WebError::DbError("Returned user had no ID".into()))?; let Some(yubikey) = YubiKey::find_by_id(&appstate.pool, key_id).await? else { error!("Yubikey with id {key_id} not found"); return Err(WebError::ObjectNotFound("YubiKey not found".into())); }; - if !session.is_admin && yubikey.user_id != user_id { + if !session.is_admin && yubikey.user_id != user.id { warn!( - "User {user_id} tried to delete yubikey {key_id} of user {} without being an admin.", - yubikey.user_id + "User {} tried to delete yubikey {key_id} of user {} without being an admin.", + user.id, yubikey.user_id ); return Err(WebError::Forbidden("Not allowed to delete YubiKey".into())); } yubikey.delete(&appstate.pool).await?; - info!("Yubikey {key_id} deleted by user {user_id}"); + info!("Yubikey {key_id} deleted by user {}", user.id); Ok(ApiResponse { json: json!({}), status: StatusCode::OK, @@ -49,24 +46,21 @@ pub async fn rename_yubikey( Json(data): Json, ) -> ApiResult { let user = user_for_admin_or_self(&appstate.pool, &session, &username).await?; - let user_id = user - .id - .ok_or(WebError::DbError("Returned user had no ID".into()))?; - debug!("User {} attempts to rename yubikey {}", user_id, key_id); + debug!("User {} attempts to rename yubikey {}", user.id, key_id); let Some(mut yubikey) = YubiKey::find_by_id(&appstate.pool, key_id).await? else { error!("Yubikey with id {key_id} not found"); return Err(WebError::ObjectNotFound("YubiKey not found".into())); }; - if !session.is_admin && yubikey.user_id != user_id { + if !session.is_admin && yubikey.user_id != user.id { warn!( - "User {user_id}, tried to rename yubikey {key_id} of user {} without being an admin.", - yubikey.user_id + "User {}, tried to rename yubikey {key_id} of user {} without being an admin.", + user.id, yubikey.user_id ); return Err(WebError::Forbidden(String::new())); } yubikey.name = data.name; yubikey.save(&appstate.pool).await?; - info!("Yubikey {:?} renamed by user {user_id}", yubikey.id); + info!("Yubikey {} renamed by user {}", yubikey.id, user.id); Ok(ApiResponse { json: json!(yubikey), status: StatusCode::OK, diff --git a/src/headers.rs b/src/headers.rs index e20a816bc0..0ceba071a1 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -1,10 +1,11 @@ use std::{borrow::Borrow, sync::Arc}; +use sqlx::PgPool; use tokio::sync::mpsc::UnboundedSender; use uaparser::{Client, Parser, UserAgentParser}; use crate::{ - db::{models::device_login::DeviceLoginEvent, DbPool, Session, User}, + db::{models::device_login::DeviceLoginEvent, Id, Session, User}, handlers::mail::send_new_device_login_email, mail::Mail, templates::TemplateError, @@ -13,15 +14,11 @@ use crate::{ #[must_use] pub fn create_user_agent_parser() -> Arc { let regexes = include_bytes!("../user_agent_header_regexes.yaml"); - Arc::new( - UserAgentParser::builder() - .build_from_bytes(regexes) - .expect("Parser creation failed"), - ) + Arc::new(UserAgentParser::from_bytes(regexes).expect("Parser creation failed")) } #[must_use] -pub fn parse_user_agent<'a>( +pub(crate) fn parse_user_agent<'a>( user_parser: &UserAgentParser, user_agent: &'a str, ) -> Option> { @@ -33,23 +30,15 @@ pub fn parse_user_agent<'a>( } #[must_use] -pub fn get_device_info(user_agent_parser: &UserAgentParser, user_agent: &str) -> Option { - let agent = parse_user_agent(user_agent_parser, user_agent); - - agent.map(|v| get_user_agent_device(&v)) -} - -#[must_use] -pub fn get_device_type(user_agent_client: Option) -> String { - if let Some(client) = user_agent_client { - get_user_agent_device(&client) - } else { - String::new() - } +pub(crate) fn get_device_info( + user_agent_parser: &UserAgentParser, + user_agent: &str, +) -> Option { + parse_user_agent(user_agent_parser, user_agent).map(|v| get_user_agent_device(&v)) } #[must_use] -pub fn get_user_agent_device(user_agent_client: &Client) -> String { +pub(crate) fn get_user_agent_device(user_agent_client: &Client) -> String { let device_type = user_agent_client .device .model @@ -81,8 +70,8 @@ pub fn get_user_agent_device(user_agent_client: &Client) -> String { } #[must_use] -pub fn get_device_login_event( - user_id: i64, +pub(crate) fn get_device_login_event( + user_id: Id, ip_address: String, event_type: String, user_agent_client: Option, @@ -91,8 +80,8 @@ pub fn get_device_login_event( .map(|client| get_user_agent_device_login_data(user_id, ip_address, event_type, &client)) } -pub fn get_user_agent_device_login_data( - user_id: i64, +pub(crate) fn get_user_agent_device_login_data( + user_id: Id, ip_address: String, event_type: String, user_agent_client: &Client, @@ -118,31 +107,29 @@ pub fn get_user_agent_device_login_data( ) } -pub async fn check_new_device_login( - pool: &DbPool, +pub(crate) async fn check_new_device_login( + pool: &PgPool, mail_tx: &UnboundedSender, session: &Session, - user: &User, + user: &User, ip_address: String, event_type: String, agent: Option>, ) -> Result<(), TemplateError> { - if let Some(user_id) = user.id { - if let Some(device_login_event) = - get_device_login_event(user_id, ip_address, event_type, agent.clone()) + eprintln!("ARSE"); + if let Some(device_login_event) = get_device_login_event(user.id, ip_address, event_type, agent) + { + if let Ok(Some(created_device_login_event)) = device_login_event + .check_if_device_already_logged_in(pool) + .await { - if let Ok(Some(created_device_login_event)) = device_login_event - .check_if_device_already_logged_in(pool) - .await - { - send_new_device_login_email( - &user.email, - mail_tx, - session, - created_device_login_event.created, - ) - .await?; - } + send_new_device_login_email( + &user.email, + mail_tx, + session, + created_device_login_event.created, + ) + .await?; } } diff --git a/src/ldap/error.rs b/src/ldap/error.rs index ac790cd339..6d6e9d806e 100644 --- a/src/ldap/error.rs +++ b/src/ldap/error.rs @@ -5,16 +5,19 @@ pub enum LdapError { Ldap(String), ObjectNotFound(String), MissingSettings, + // TODO: include the error + Database, } impl fmt::Display for LdapError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - LdapError::Ldap(msg) => write!(f, "LDAP error: {msg}"), - LdapError::ObjectNotFound(msg) => write!(f, "Object not found: {msg}"), - LdapError::MissingSettings => { - write!(f, "LDAP settings are missing.") + Self::Ldap(msg) => write!(f, "LDAP error: {msg}"), + Self::ObjectNotFound(msg) => write!(f, "Object not found: {msg}"), + Self::MissingSettings => { + write!(f, "LDAP settings are missing") } + Self::Database => write!(f, "Database error"), } } } diff --git a/src/ldap/mod.rs b/src/ldap/mod.rs index 770a0a1a3d..a68111516b 100644 --- a/src/ldap/mod.rs +++ b/src/ldap/mod.rs @@ -4,7 +4,7 @@ use ldap3::{drive, Ldap, LdapConnAsync, Mod, Scope, SearchEntry}; use sqlx::PgExecutor; use self::error::LdapError; -use crate::db::{self, Settings, User}; +use crate::db::{self, Id, Settings, User}; pub mod error; pub mod hash; @@ -117,6 +117,7 @@ impl LDAPConnection { ldap.simple_bind(&config.ldap_bind_username, password.expose_secret()) .await? .success()?; + Ok(Self { config, ldap }) } @@ -133,6 +134,7 @@ impl LDAPConnection { .await? .success()?; info!("Performed LDAP user search with filter = {filter}"); + Ok(rs.into_iter().map(SearchEntry::construct).collect()) } @@ -160,6 +162,7 @@ impl LDAPConnection { debug!("Adding object {dn}"); self.ldap.add(dn, attrs).await?.success()?; info!("Added object {dn}"); + Ok(()) } @@ -178,6 +181,7 @@ impl LDAPConnection { } } info!("Modified LDAP object {old_dn}"); + Ok(()) } @@ -186,6 +190,7 @@ impl LDAPConnection { debug!("Deleting LDAP object {dn}"); self.ldap.delete(dn).await?; info!("Deleted LDAP object {dn}"); + Ok(()) } @@ -224,7 +229,7 @@ impl LDAPConnection { } /// Adds user to LDAP. - pub async fn add_user(&mut self, user: &User, password: &str) -> Result<(), LdapError> { + pub async fn add_user(&mut self, user: &User, password: &str) -> Result<(), LdapError> { debug!("Adding LDAP user {}", user.username); let dn = self.config.user_dn(&user.username); let ssha_password = hash::salted_sha1_hash(password); @@ -232,17 +237,19 @@ impl LDAPConnection { self.add(&dn, user.as_ldap_attrs(&ssha_password, &ht_password)) .await?; info!("Added LDAP user {}", user.username); + Ok(()) } /// Modifies LDAP user. - pub async fn modify_user(&mut self, username: &str, user: &User) -> Result<(), LdapError> { + pub async fn modify_user(&mut self, username: &str, user: &User) -> Result<(), LdapError> { debug!("Modifying user {username}"); let old_dn = self.config.user_dn(username); let new_dn = self.config.user_dn(&user.username); self.modify(&old_dn, &new_dn, user.as_ldap_mod(&self.config)) .await?; info!("Modified user {username}"); + Ok(()) } @@ -252,6 +259,7 @@ impl LDAPConnection { let dn = self.config.user_dn(username); self.delete(&dn).await?; info!("Deleted user {username}"); + Ok(()) } @@ -271,6 +279,7 @@ impl LDAPConnection { ) .await?; info!("Password set for user {username}"); + Ok(()) } @@ -309,6 +318,7 @@ impl LDAPConnection { ) .await?; info!("Modified LDAP group {groupname}"); + Ok(()) } @@ -346,6 +356,7 @@ impl LDAPConnection { )], ) .await?; + Ok(()) } @@ -366,6 +377,7 @@ impl LDAPConnection { )], ) .await?; + Ok(()) } } diff --git a/src/ldap/model.rs b/src/ldap/model.rs index e16d0ca90d..a817feaf03 100644 --- a/src/ldap/model.rs +++ b/src/ldap/model.rs @@ -17,7 +17,9 @@ impl User { get_value(entry, "mobile"), ) } +} +impl User { #[must_use] pub fn as_ldap_mod(&self, config: &LDAPConfig) -> Vec> { let mut changes = vec![ diff --git a/src/ldap/utils.rs b/src/ldap/utils.rs index 1fa8e07e5f..1e80af19da 100644 --- a/src/ldap/utils.rs +++ b/src/ldap/utils.rs @@ -1,20 +1,28 @@ -use sqlx::PgExecutor; +use sqlx::{PgExecutor, PgPool}; use super::{error::LdapError, LDAPConnection}; -use crate::db::{DbPool, Group, User}; +use crate::db::{Group, Id, User}; pub async fn user_from_ldap( - pool: &DbPool, + pool: &PgPool, username: &str, password: &str, -) -> Result { +) -> Result, LdapError> { let mut ldap_connection = LDAPConnection::create(pool).await?; - let mut user = ldap_connection.get_user(username, password).await?; - let _result = user.save(pool).await; // FIXME: do not ignore errors - Ok(user) + // FIXME: do not ignore errors + ldap_connection + .get_user(username, password) + .await? + .save(pool) + .await + .map_err(|_| LdapError::Database) } -pub async fn ldap_add_user<'e, E>(executor: E, user: &User, password: &str) -> Result<(), LdapError> +pub async fn ldap_add_user<'e, E>( + executor: E, + user: &User, + password: &str, +) -> Result<(), LdapError> where E: PgExecutor<'e>, { @@ -29,7 +37,7 @@ where pub async fn ldap_modify_user<'e, E>( executor: E, username: &str, - user: &User, + user: &User, ) -> Result<(), LdapError> where E: PgExecutor<'e>, diff --git a/src/lib.rs b/src/lib.rs index 275d08d498..f3f2f3deb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,23 +5,29 @@ use std::{ }; use anyhow::anyhow; +use assets::{index, svg, web_asset}; use axum::{ http::{Request, StatusCode}, routing::{delete, get, patch, post, put}, - serve, Extension, Router, + serve, Extension, Json, Router, }; - -use assets::{index, svg, web_asset}; -use handlers::ssh_authorized_keys::{ - add_authentication_key, delete_authentication_key, fetch_authentication_keys, +use enterprise::handlers::{ + check_enterprise_status, + enterprise_settings::{get_enterprise_settings, patch_enterprise_settings}, + openid_login::{auth_callback, get_auth_info}, + openid_providers::{add_openid_provider, delete_openid_provider, get_current_openid_provider}, }; use handlers::{ group::{bulk_assign_to_groups, list_groups_info}, - ssh_authorized_keys::rename_authentication_key, + ssh_authorized_keys::{ + add_authentication_key, delete_authentication_key, fetch_authentication_keys, + rename_authentication_key, + }, yubikey::{delete_yubikey, rename_yubikey}, }; use ipnetwork::IpNetwork; use secrecy::ExposeSecret; +use sqlx::PgPool; use tokio::{ net::TcpListener, sync::{ @@ -33,7 +39,33 @@ use tokio::{ use tower_http::trace::{DefaultOnResponse, TraceLayer}; use tracing::Level; use uaparser::UserAgentParser; +use utoipa::{ + openapi::security::{ApiKey, ApiKeyValue, SecurityScheme}, + Modify, OpenApi, +}; +use utoipa_swagger_ui::SwaggerUi; +#[cfg(feature = "wireguard")] +use self::handlers::wireguard::{ + add_device, add_user_devices, create_network, create_network_token, delete_device, + delete_network, download_config, gateway_status, get_device, import_network, list_devices, + list_networks, list_user_devices, modify_device, modify_network, network_details, + network_stats, remove_gateway, user_stats, +}; +#[cfg(feature = "worker")] +use self::handlers::worker::{ + create_job, create_worker_token, job_status, list_workers, remove_worker, +}; +#[cfg(feature = "openid")] +use self::handlers::{ + openid_clients::{ + add_openid_client, change_openid_client, change_openid_client_state, delete_openid_client, + get_openid_client, list_openid_clients, + }, + openid_flow::{ + authorization, discovery_keys, openid_configuration, secure_authorization, token, userinfo, + }, +}; use self::{ appstate::AppState, auth::{Claims, ClaimsType}, @@ -41,7 +73,7 @@ use self::{ db::{ init_db, models::wireguard::{DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL}, - AppEvent, DbPool, Device, GatewayEvent, User, WireguardNetwork, + AppEvent, Device, GatewayEvent, User, WireguardNetwork, }, handlers::{ auth::{ @@ -74,28 +106,6 @@ use self::{ }, mail::Mail, }; - -#[cfg(feature = "wireguard")] -use self::handlers::wireguard::{ - add_device, add_user_devices, create_network, create_network_token, delete_device, - delete_network, download_config, gateway_status, get_device, import_network, list_devices, - list_networks, list_user_devices, modify_device, modify_network, network_details, - network_stats, remove_gateway, user_stats, -}; -#[cfg(feature = "worker")] -use self::handlers::worker::{ - create_job, create_worker_token, job_status, list_workers, remove_worker, -}; -#[cfg(feature = "openid")] -use self::handlers::{ - openid_clients::{ - add_openid_client, change_openid_client, change_openid_client_state, delete_openid_client, - get_openid_client, list_openid_clients, - }, - openid_flow::{ - authorization, discovery_keys, openid_configuration, secure_authorization, token, userinfo, - }, -}; #[cfg(any(feature = "openid", feature = "worker"))] use self::{ auth::failed_login::FailedLoginMap, @@ -109,6 +119,7 @@ pub mod assets; pub mod auth; pub mod config; pub mod db; +pub mod enterprise; mod error; pub mod grpc; pub mod handlers; @@ -130,7 +141,7 @@ extern crate tracing; #[macro_use] extern crate serde; -pub static VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "-", env!("VERGEN_GIT_SHA")); pub static SERVER_CONFIG: OnceCell = OnceCell::const_new(); pub(crate) fn server_config() -> &'static DefGuardConfig { @@ -142,6 +153,115 @@ pub(crate) fn server_config() -> &'static DefGuardConfig { // WireGuard key length in bytes. pub(crate) const KEY_LENGTH: usize = 32; +mod openapi { + use db::{ + models::device::{ModifyDevice, UserDevice}, + AddDevice, UserDetails, UserInfo, + }; + use error::WebError; + use handlers::{ + group::{self, BulkAssignToGroupsRequest, Groups}, + user::{self, WalletInfoShort}, + wireguard as device, + wireguard::AddDeviceResult, + ApiResponse, EditGroupInfo, GroupInfo, PasswordChange, PasswordChangeSelf, + StartEnrollmentRequest, Username, WalletChange, WalletSignature, + }; + use utoipa::OpenApi; + + use super::*; + + #[derive(OpenApi)] + #[openapi( + modifiers(&SecurityAddon), + paths( + // /user + user::list_users, + user::get_user, + user::add_user, + user::start_enrollment, + user::start_remote_desktop_configuration, + user::username_available, + user::modify_user, + user::delete_user, + user::change_self_password, + user::change_password, + user::reset_password, + user::wallet_challenge, + user::set_wallet, + user::update_wallet, + user::delete_wallet, + user::delete_security_key, + user::me, + user::delete_authorized_app, + // /device + device::add_device, + device::modify_device, + device::get_device, + device::delete_device, + device::list_devices, + device::list_user_devices, + // /group + group::bulk_assign_to_groups, + group::list_groups_info, + group::list_groups, + group::get_group, + group::create_group, + group::modify_group, + group::delete_group, + group::add_group_member, + group::remove_group_member, + ), + components( + schemas( + ApiResponse, UserInfo, WebError, UserDetails, UserDevice, Groups, Username, StartEnrollmentRequest, PasswordChangeSelf, PasswordChange, WalletInfoShort, WalletSignature, WalletChange, AddDevice, AddDeviceResult, Device, ModifyDevice, BulkAssignToGroupsRequest, GroupInfo, EditGroupInfo + ), + ), + tags( + (name = "user", description = " +Endpoints that allow to control user data. + +Available actions: +- list all users +- CRUD mechanism for handling users +- operations on user wallet +- operations on security key and authorized app +- change user password. + "), + (name = "device", description = " +Endpoints that allow to control devices in your network. + +Available actions: +- list all devices or user devices +- CRUD mechanism for handling devices. + "), + (name = "group", description = " +Endpoints that allow to control groups in your network. + +Available actions: +- list all groups +- CRUD mechanism for handling groups +- add or delete a group member. + ") + ) + )] + pub struct ApiDoc; + + struct SecurityAddon; + + impl Modify for SecurityAddon { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + if let Some(components) = openapi.components.as_mut() { + // TODO: add an appropriate security schema + components.add_security_scheme( + "api_key", + SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("user_apikey"))), + ); + } + } + } +} + /// Simple health-check. async fn health_check() -> &'static str { "alive" @@ -151,6 +271,10 @@ async fn handle_404() -> (StatusCode, &'static str) { (StatusCode::NOT_FOUND, "Not found") } +async fn openapi() -> Json { + Json(openapi::ApiDoc::openapi()) +} + pub fn build_webapp( webhook_tx: UnboundedSender, webhook_rx: UnboundedReceiver, @@ -158,7 +282,7 @@ pub fn build_webapp( mail_tx: UnboundedSender, worker_state: Arc>, gateway_state: Arc>, - pool: DbPool, + pool: PgPool, user_agent_parser: Arc, failed_logins: Arc>, ) -> Router { @@ -176,6 +300,7 @@ pub fn build_webapp( .route("/health", get(health_check)) .route("/info", get(get_app_info)) .route("/ssh_authorized_keys", get(get_authorized_keys)) + .route("/api-docs", get(openapi)) // /auth .route("/auth", post(authenticate)) .route("/auth/logout", post(logout)) @@ -265,6 +390,9 @@ pub fn build_webapp( .route("/settings/:id", put(set_default_branding)) // settings for frontend .route("/settings_essentials", get(get_settings_essentials)) + // enterprise settings + .route("/settings_enterprise", get(get_enterprise_settings)) + .route("/settings_enterprise", patch(patch_enterprise_settings)) // support .route("/support/configuration", get(configuration)) .route("/support/logs", get(logs)) @@ -279,6 +407,18 @@ pub fn build_webapp( .route("/ldap/test", get(test_ldap_settings)), ); + // Enterprise features + let webapp = webapp.nest( + "/api/v1/openid", + Router::new() + .route("/provider", get(get_current_openid_provider)) + .route("/provider", post(add_openid_provider)) + .route("/provider/:name", delete(delete_openid_provider)) + .route("/callback", post(auth_callback)) + .route("/auth_info", get(get_auth_info)), + ); + let webapp = webapp.route("/api/v1/enterprise_status", get(check_enterprise_status)); + #[cfg(feature = "openid")] let webapp = webapp .nest( @@ -305,6 +445,7 @@ pub fn build_webapp( let webapp = webapp.nest( "/api/v1", Router::new() + // FIXME: change /device/:device_id to /device/:username .route("/device/:device_id", post(add_device)) .route("/device/:device_id", put(modify_device)) .route("/device/:device_id", get(get_device)) @@ -345,6 +486,9 @@ pub fn build_webapp( .layer(Extension(worker_state)), ); + let swagger = + SwaggerUi::new("/api-docs").url("/api-docs/openapi.json", openapi::ApiDoc::openapi()); + webapp .with_state(AppState::new( pool, @@ -366,6 +510,7 @@ pub fn build_webapp( }) .on_response(DefaultOnResponse::new().level(Level::INFO)), ) + .merge(swagger) } /// Runs core web server exposing REST API. @@ -376,7 +521,7 @@ pub async fn run_web_server( webhook_rx: UnboundedReceiver, wireguard_tx: Sender, mail_tx: UnboundedSender, - pool: DbPool, + pool: PgPool, user_agent_parser: Arc, failed_logins: Arc>, ) -> Result<(), anyhow::Error> { @@ -456,8 +601,7 @@ pub async fn init_dev_env(config: &DefGuardConfig) { network .save(&mut *transaction) .await - .expect("Could not save network"); - network + .expect("Could not save network") }; if Device::find_by_pubkey( @@ -471,15 +615,14 @@ pub async fn init_dev_env(config: &DefGuardConfig) { info!("Test device exists already, skipping creation..."); } else { info!("Creating test device"); - let mut device = Device::new( + let device = Device::new( "TestDevice".to_string(), "gQYL5eMeFDj0R+lpC7oZyIl0/sNVmQDC6ckP7husZjc=".to_string(), 1, - ); - device - .save(&mut *transaction) - .await - .expect("Could not save device"); + ) + .save(&mut *transaction) + .await + .expect("Could not save device"); device .assign_network_ip(&mut transaction, &network, None) .await @@ -488,14 +631,14 @@ pub async fn init_dev_env(config: &DefGuardConfig) { #[cfg(feature = "openid")] for app_id in 1..=3 { - let mut app = OAuth2Client::new( + OAuth2Client::new( vec![format!("https://app-{app_id}.com")], vec!["openid".into(), "profile".into(), "email".into()], format!("app-{app_id}"), - ); - app.save(&mut *transaction) - .await - .expect("Could not save oauth2client"); + ) + .save(&mut *transaction) + .await + .expect("Could not save oauth2client"); } transaction .commit() @@ -509,7 +652,7 @@ pub async fn init_dev_env(config: &DefGuardConfig) { /// Meant to be used to automate setting up a new defguard instance. /// Does not handle assigning device IPs, since no device should exist at this point. pub async fn init_vpn_location( - pool: &DbPool, + pool: &PgPool, args: &InitVpnLocationArgs, ) -> Result { // check if a VPN location exists already @@ -521,7 +664,7 @@ pub async fn init_vpn_location( }; // create a new network - let mut network = WireguardNetwork::new( + let network = WireguardNetwork::new( args.name.clone(), args.address, args.port, @@ -531,15 +674,15 @@ pub async fn init_vpn_location( false, DEFAULT_KEEPALIVE_INTERVAL, DEFAULT_DISCONNECT_THRESHOLD, - )?; - network.save(pool).await?; - let network_id = network.get_id()?; + )? + .save(pool) + .await?; // generate gateway token let token = Claims::new( ClaimsType::Gateway, - format!("DEFGUARD-NETWORK-{network_id}"), - network_id.to_string(), + format!("DEFGUARD-NETWORK-{}", network.id), + network.id.to_string(), u32::MAX.into(), ) .to_jwt()?; diff --git a/src/mail.rs b/src/mail.rs index ebfc21aff6..6b93b326f0 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -6,15 +6,15 @@ use lettre::{ transport::smtp::{authentication::Credentials, response::Response}, Address, AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor, }; -use sqlx::{Pool, Postgres}; +use sqlx::PgPool; use thiserror::Error; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use crate::db::{models::settings::SmtpEncryption, Settings}; -static SMTP_TIMEOUT_SECONDS: u64 = 15; +const SMTP_TIMEOUT_SECONDS: u64 = 15; -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum MailError { #[error(transparent)] LettreError(#[from] lettre::error::Error), @@ -49,12 +49,12 @@ struct SmtpSettings { } impl SmtpSettings { - /// Retrieves Settings object from database and builds SmtpSettings - pub async fn get(db: &Pool) -> Result { + /// Retrieves `Settings` from database and builds `SmtpSettings`. + pub async fn get(db: &PgPool) -> Result { Self::from_settings(Self::get_settings(db).await?) } - /// Constructs SmtpSettings object from Settings. Returns error if SMTP settings are incomplete. + /// Constructs `SmtpSettings` from `Settings`. Returns error if `SmtpSettings` are incomplete. pub fn from_settings(settings: Settings) -> Result { if let (Some(server), Some(port), encryption, Some(user), Some(password), Some(sender)) = ( settings.smtp_server, @@ -79,10 +79,8 @@ impl SmtpSettings { } /// Retrieves Settings object from database - async fn get_settings(db: &Pool) -> Result { - Settings::find_by_id(db, 1) - .await? - .ok_or(MailError::EmptySettings) + async fn get_settings(db: &PgPool) -> Result { + Settings::get(db).await?.ok_or(MailError::EmptySettings) } } @@ -143,11 +141,11 @@ impl Mail { struct MailHandler { rx: UnboundedReceiver, - db: Pool, + db: PgPool, } impl MailHandler { - pub fn new(rx: UnboundedReceiver, db: Pool) -> Self { + pub fn new(rx: UnboundedReceiver, db: PgPool) -> Self { Self { rx, db } } @@ -236,6 +234,6 @@ impl MailHandler { } /// Builds MailHandler and runs it. -pub async fn run_mail_handler(rx: UnboundedReceiver, db: Pool) { +pub async fn run_mail_handler(rx: UnboundedReceiver, db: PgPool) { MailHandler::new(rx, db).run().await; } diff --git a/src/secret.rs b/src/secret.rs index ff062e1959..9eed4570f1 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -1,14 +1,8 @@ -use std::convert::Infallible; -use std::error::Error; -use std::str::FromStr; +use std::{convert::Infallible, error::Error, str::FromStr}; use secrecy::{ExposeSecret, Secret}; use serde::{Deserialize, Serialize}; -use sqlx::{ - database::{HasArguments, HasValueRef}, - encode::IsNull, - Database, Decode, Encode, Type, -}; +use sqlx::{encode::IsNull, Database, Decode, Encode, Type}; /// Wrapper for secrecy Secret struct which implements sqlx Postgres #[derive(Clone, Deserialize, Debug)] @@ -42,9 +36,7 @@ impl<'q, DB: Database> Decode<'q, DB> for SecretString where String: Decode<'q, DB>, { - fn decode( - value: >::ValueRef, - ) -> Result> { + fn decode(value: ::ValueRef<'q>) -> Result> { >::decode(value).map(|v| Self(Secret::from(v))) } } @@ -53,7 +45,10 @@ impl<'q, DB: Database> Encode<'q, DB> for SecretString where String: Encode<'q, DB>, { - fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + fn encode_by_ref( + &self, + buf: &mut ::ArgumentBuffer<'q>, + ) -> Result> { >::encode_by_ref(self.0.expose_secret(), buf) } diff --git a/src/support.rs b/src/support.rs index b43007577a..d20cdc283d 100644 --- a/src/support.rs +++ b/src/support.rs @@ -2,9 +2,10 @@ use std::{collections::HashMap, fmt::Display}; use serde::Serialize; use serde_json::{json, value::to_value, Value}; +use sqlx::PgPool; use crate::{ - db::{models::device::WireguardNetworkDevice, DbPool, Settings, User, WireguardNetwork}, + db::{models::device::WireguardNetworkDevice, Id, Settings, User, WireguardNetwork}, server_config, VERSION, }; @@ -17,9 +18,9 @@ fn unwrap_json(result: Result) -> Value { } /// Dumps all data that could be used for debugging. -pub async fn dump_config(db: &DbPool) -> Value { +pub async fn dump_config(db: &PgPool) -> Value { // App settings DB records - let settings = match Settings::find_by_id(db, 1).await { + let settings = match Settings::get(db).await { Ok(Some(mut settings)) => { settings.smtp_password = None; json!(settings) @@ -31,14 +32,11 @@ pub async fn dump_config(db: &DbPool) -> Value { let (networks, devices) = match WireguardNetwork::all(db).await { Ok(networks) => { // Devices for each network - let mut devices = HashMap::::new(); + let mut devices = HashMap::::new(); for network in &networks { - let Some(network_id) = network.id else { - continue; - }; devices.insert( - network_id, - unwrap_json(WireguardNetworkDevice::all_for_network(db, network_id).await), + network.id, + unwrap_json(WireguardNetworkDevice::all_for_network(db, network.id).await), ); } ( diff --git a/src/templates.rs b/src/templates.rs index 4d803d43bb..38890abfa7 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -4,7 +4,7 @@ use tera::{Context, Tera}; use thiserror::Error; use crate::{ - db::{MFAMethod, Session, User}, + db::{Id, MFAMethod, Session, User}, server_config, VERSION, }; @@ -32,9 +32,6 @@ static MAIL_PASSWORD_RESET_START: &str = static MAIL_PASSWORD_RESET_SUCCESS: &str = include_str!("../templates/mail_password_reset_success.tera"); -#[allow(dead_code)] -static MAIL_DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:00Z"; - #[derive(Error, Debug)] pub enum TemplateError { #[error("Failed to generate email MFA code")] @@ -56,7 +53,7 @@ pub fn get_base_tera( // supply context required by base context.insert("application_version", &VERSION); let now = Utc::now(); - let current_year = format!("{:04}", &now.year()); + let current_year = format!("{:04}", now.year()); context.insert("current_year", ¤t_year); context.insert("date_now", &now.format("%A, %B %d, %Y at %r").to_string()); @@ -90,6 +87,7 @@ pub fn enrollment_start_mail( mut enrollment_service_url: Url, enrollment_token: &str, ) -> Result { + debug!("Render an enrollment start mail template for the user."); let (mut tera, mut context) = get_base_tera(Some(context), None, None, None)?; // add required context @@ -114,6 +112,7 @@ pub fn desktop_start_mail( enrollment_service_url: &Url, enrollment_token: &str, ) -> Result { + debug!("Render a mail template for desktop activation."); let (mut tera, mut context) = get_base_tera(Some(context), None, None, None)?; tera.add_raw_template("mail_desktop_start", MAIL_DESKTOP_START)?; @@ -131,6 +130,7 @@ pub fn enrollment_welcome_mail( ip_address: Option<&str>, device_info: Option<&str>, ) -> Result { + debug!("Render a welcome mail template for user enrollment."); let (mut tera, mut context) = get_base_tera(None, None, ip_address, device_info)?; tera.add_raw_template("mail_enrollment_welcome", MAIL_ENROLLMENT_WELCOME)?; @@ -145,12 +145,13 @@ pub fn enrollment_welcome_mail( } // notification sent to admin after user completes enrollment -pub fn enrollment_admin_notification( - user: &User, - admin: &User, +pub fn enrollment_admin_notification( + user: &User, + admin: &User, ip_address: &str, device_info: Option<&str>, ) -> Result { + debug!("Render an admin notification mail template."); let (mut tera, mut context) = get_base_tera(None, None, Some(ip_address), device_info)?; tera.add_raw_template( @@ -161,6 +162,7 @@ pub fn enrollment_admin_notification( context.insert("last_name", &user.last_name); context.insert("admin_first_name", &admin.first_name); context.insert("admin_last_name", &admin.last_name); + Ok(tera.render("mail_enrollment_admin_notification", &context)?) } @@ -184,6 +186,7 @@ pub fn new_device_added_mail( ip_address: Option<&str>, device_info: Option<&str>, ) -> Result { + debug!("Render a new device added mail template for the user."); let (mut tera, mut context) = get_base_tera(None, None, ip_address, device_info)?; context.insert("device_name", device_name); context.insert("public_key", public_key); @@ -249,23 +252,33 @@ pub fn gateway_disconnected_mail( Ok(tera.render("mail_gateway_disconnected", &context)?) } -pub fn email_mfa_activation_mail(code: u32, session: &Session) -> Result { +pub fn email_mfa_activation_mail( + user: &User, + code: &str, + session: &Session, +) -> Result { let (mut tera, mut context) = get_base_tera(None, Some(session), None, None)?; let timeout = server_config().mfa_code_timeout; // zero-pad code to make sure it's always 6 digits long context.insert("code", &format!("{code:0>6}")); context.insert("timeout", &timeout.to_string()); + context.insert("name", &user.first_name); tera.add_raw_template("mail_email_mfa_activation", MAIL_EMAIL_MFA_ACTIVATION)?; Ok(tera.render("mail_email_mfa_activation", &context)?) } -pub fn email_mfa_code_mail(code: u32, session: Option<&Session>) -> Result { +pub fn email_mfa_code_mail( + user: &User, + code: &str, + session: Option<&Session>, +) -> Result { let (mut tera, mut context) = get_base_tera(None, session, None, None)?; let timeout = server_config().mfa_code_timeout; // zero-pad code to make sure it's always 6 digits long context.insert("code", &format!("{code:0>6}")); context.insert("timeout", &timeout.to_string()); + context.insert("name", &user.first_name); tera.add_raw_template("mail_email_mfa_code", MAIL_EMAIL_MFA_CODE)?; Ok(tera.render("mail_email_mfa_code", &context)?) @@ -308,10 +321,10 @@ pub fn email_password_reset_success_mail( #[cfg(test)] mod test { - use crate::{config::DefGuardConfig, SERVER_CONFIG}; use claims::assert_ok; use super::*; + use crate::{config::DefGuardConfig, SERVER_CONFIG}; fn get_welcome_context() -> Context { let mut context = Context::new(); @@ -351,7 +364,7 @@ mod test { #[test] fn test_enrollment_start_mail() { - SERVER_CONFIG.set(DefGuardConfig::default()).unwrap(); + let _ = SERVER_CONFIG.set(DefGuardConfig::default()); assert_ok!(enrollment_start_mail( Context::new(), Url::parse("http://localhost:8080").unwrap(), diff --git a/src/wg_config.rs b/src/wg_config.rs index d57103ec56..0e65ac62f8 100644 --- a/src/wg_config.rs +++ b/src/wg_config.rs @@ -15,7 +15,7 @@ use crate::{ KEY_LENGTH, }; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct ImportedDevice { pub user_id: Option, pub name: String, @@ -143,6 +143,7 @@ pub fn parse_wireguard_config( #[cfg(test)] mod test { use super::*; + use crate::db::NoId; #[test] fn test_parse_config() { @@ -168,7 +169,7 @@ mod test { network.prvkey, "GAA2X3DW0WakGVx+DsGjhDpTgg50s1MlmrLf24Psrlg=" ); - assert_eq!(network.id, None); + assert_eq!(network.id, NoId); assert_eq!(network.name, "Y5ewP5RXstQd71gkmS/M0xL8wi0yVbbVY/ocLM4cQ1Y="); assert_eq!(network.address, "10.0.0.1/24".parse().unwrap()); assert_eq!(network.port, 55055); diff --git a/src/wireguard_peer_disconnect.rs b/src/wireguard_peer_disconnect.rs index 70b4926fb0..e8d0fc0d41 100644 --- a/src/wireguard_peer_disconnect.rs +++ b/src/wireguard_peer_disconnect.rs @@ -4,18 +4,20 @@ //! it should be removed from gateway configuration and marked as "not allowed", //! which enforces an authentication requirement to connect again. +use std::time::Duration; + +use sqlx::{query_as, Error as SqlxError, PgPool}; +use thiserror::Error; +use tokio::{sync::broadcast::Sender, time::sleep}; + use crate::db::{ models::{ device::{DeviceInfo, DeviceNetworkInfo, WireguardNetworkDevice}, error::ModelError, wireguard::WireguardNetworkError, }, - DbPool, Device, GatewayEvent, WireguardNetwork, + Device, GatewayEvent, Id, WireguardNetwork, }; -use sqlx::{query_as, Error as SqlxError}; -use std::time::Duration; -use thiserror::Error; -use tokio::{sync::broadcast::Sender, time::sleep}; // How long to sleep between loop iterations const DISCONNECT_LOOP_SLEEP_SECONDS: u64 = 60; // 1 minute @@ -36,7 +38,7 @@ pub enum PeerDisconnectError { /// /// Run with a specified frequency and disconnect all inactive peers in MFA-protected locations. pub async fn run_periodic_peer_disconnect( - pool: DbPool, + pool: PgPool, wireguard_tx: Sender, ) -> Result<(), PeerDisconnectError> { info!("Starting periodic disconnect of inactive devices in MFA-protected locations"); @@ -45,9 +47,9 @@ pub async fn run_periodic_peer_disconnect( // get all MFA-protected locations let locations = query_as!( - WireguardNetwork, + WireguardNetwork::, "SELECT \ - id as \"id?\", name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ + id, name, address, port, pubkey, prvkey, endpoint, dns, allowed_ips, \ connected_at, mfa_enabled, keepalive_interval, peer_disconnect_threshold \ FROM wireguard_network WHERE mfa_enabled = true", ) @@ -57,7 +59,6 @@ pub async fn run_periodic_peer_disconnect( // loop over all locations for location in locations { debug!("Fetching inactive devices for location {location}"); - let location_id = location.get_id()?; let devices = query_as!( Device, "WITH stats AS ( \ @@ -66,14 +67,14 @@ pub async fn run_periodic_peer_disconnect( WHERE network = $1 \ ORDER BY device_id, collected_at DESC \ ) \ - SELECT d.id as \"id?\", d.name, d.wireguard_pubkey, d.user_id, d.created \ + SELECT d.id, d.name, d.wireguard_pubkey, d.user_id, d.created \ FROM device d \ JOIN wireguard_network_device wnd ON wnd.device_id = d.id \ LEFT JOIN stats on d.id = stats.device_id \ WHERE wnd.wireguard_network_id = $1 AND wnd.is_authorized = true AND \ (wnd.authorized_at IS NULL OR (NOW() - wnd.authorized_at) > $2 * interval '1 second') AND \ (stats.latest_handshake IS NULL OR (NOW() - stats.latest_handshake) > $2 * interval '1 second')", - location_id, + location.id, f64::from(location.peer_disconnect_threshold) ) .fetch_all(&pool) @@ -81,14 +82,13 @@ pub async fn run_periodic_peer_disconnect( for device in devices { debug!("Processing inactive device {device}"); - let device_id = device.get_id()?; // start transaction let mut transaction = pool.begin().await?; // get network config for device if let Some(mut device_network_config) = - WireguardNetworkDevice::find(&mut *transaction, device_id, location_id).await? + WireguardNetworkDevice::find(&mut *transaction, device.id, location.id).await? { info!("Marking device {device} as not authorized to connect to location {location}"); // change `is_authorized` value for device @@ -101,7 +101,7 @@ pub async fn run_periodic_peer_disconnect( let device_info = DeviceInfo { device, network_info: vec![DeviceNetworkInfo { - network_id: location_id, + network_id: location.id, device_wireguard_ip: device_network_config.wireguard_ip, preshared_key: device_network_config.preshared_key, is_authorized: device_network_config.is_authorized, diff --git a/src/wireguard_stats_purge.rs b/src/wireguard_stats_purge.rs index 2280a075fb..3a9580c2d4 100644 --- a/src/wireguard_stats_purge.rs +++ b/src/wireguard_stats_purge.rs @@ -1,10 +1,12 @@ -use crate::db::{DbPool, WireguardPeerStats}; +use std::time::Duration; + use chrono::{DateTime, Duration as ChronoDuration, NaiveDateTime, Utc}; use humantime::format_duration; -use sqlx::{query, query_scalar, Error as SqlxError, PgExecutor}; -use std::time::Duration; +use sqlx::{query, query_scalar, Error as SqlxError, PgExecutor, PgPool}; use tokio::time::sleep; +use crate::db::WireguardPeerStats; + // How long to sleep between loop iterations const PURGE_LOOP_SLEEP_SECONDS: u64 = 300; // 5 minutes @@ -14,7 +16,7 @@ impl WireguardPeerStats { /// At least one record is retained for each device & network combination, /// even when older than set threshold. pub async fn purge_old_stats( - pool: &DbPool, + pool: &PgPool, stats_purge_threshold: Duration, ) -> Result<(), SqlxError> { let start = Utc::now(); @@ -92,7 +94,7 @@ impl WireguardPeerStats { } pub async fn run_periodic_stats_purge( - pool: DbPool, + pool: PgPool, stats_purge_frequency: Duration, stats_purge_threshold: Duration, ) -> Result<(), SqlxError> { diff --git a/templates/base.tera b/templates/base.tera index b74d4fdd6a..181fee1de2 100644 --- a/templates/base.tera +++ b/templates/base.tera @@ -153,9 +153,9 @@ - Defguard logo + Defguard logo @@ -236,7 +236,7 @@ width="100%"> - {% if date_now %}

- Date: {{ date_now | safe }} + Date: {{ date_now | safe }}

{% endif %} {% if ip_address %}

- IP Address: {{ ip_address | safe }} + IP Address: {{ ip_address | safe }}

{% endif %} {% if device_type %}

- Device type: {{ device_type | safe }} + Device type: {{ device_type | safe }}

{% endif %} @@ -483,7 +483,7 @@ -
@@ -494,10 +494,11 @@ diff --git a/templates/macros.tera b/templates/macros.tera index 994c479a51..a05ab8ed03 100644 --- a/templates/macros.tera +++ b/templates/macros.tera @@ -53,13 +53,14 @@ {% endmacro text_section %} -{% macro paragraph(content="", color="#222", font_size="12px", align="left", line_height="120%") %} +{% macro paragraph(content="", color="#222", font_size="12px", align="left", line_height="120%", font_weight="400") %}

{{ content | safe }}

@@ -128,7 +129,7 @@ size="12px", line_height="120%", weight="400") %} {% endmacro link %} -{% macro title(content="") %} +{% macro title(content="", font_size="28px") %}
- Copyright © {{ current_year }} teonite
Sent by Defguard - v.{{ application_version }}
+ style="font-family:Poppins, Arial;font-size:12px;font-weight:400;line-height:normal;color:#899CA8; text-align: center;"> +
Copyright © {{ current_year }} teonite
+
Sent by Defguard v.{{ application_version }}
+
@@ -162,7 +163,7 @@ size="12px", line_height="120%", weight="400") %} ">
- use this code to complete MFA setup."), - macros::paragraph(content="The code is valid for " ~ timeout ~ "."), + macros::title(content="" ~ code ~ "", font_size="45px"), + macros::spacer(height="40px"), + macros::paragraph(content="The code is valid for " ~ timeout ~ ".", align="center", font_size="15px"), ] %} {{ macros::text_section(content_array=section_content) }} {{ macros::spacer(height="10px") }} diff --git a/templates/mail_email_mfa_code.tera b/templates/mail_email_mfa_code.tera index b3584e93e6..e0d468eb55 100644 --- a/templates/mail_email_mfa_code.tera +++ b/templates/mail_email_mfa_code.tera @@ -6,8 +6,16 @@ code -> 6-digit zero-padded verification code {% import "macros.tera" as macros %} {% block mail_content %} {% set section_content = [ - macros::paragraph(content="Your code is: " ~ code ~ " - use this code to complete logging in with defguard"), - macros::paragraph(content="The code is valid for " ~ timeout ~ "."), + macros::title(content="Hello, " ~ name), + macros::paragraph(content="It seems like you are trying to login to defguard.", line_height="0%", align="center"), + macros::paragraph(content="Here is the code you need to access your account:", align="center"), +] %} +{{ macros::text_section(content_array=section_content) }} +{{ macros::spacer(height="40px") }} +{% set section_content = [ + macros::title(content="" ~ code ~ "", font_size="45px"), + macros::spacer(height="40px"), + macros::paragraph(content="The code is valid for " ~ timeout ~ ".", align="center", font_size="15px"), ] %} {{ macros::text_section(content_array=section_content) }} {{ macros::spacer(height="10px") }} diff --git a/templates/mail_enrollment_start.tera b/templates/mail_enrollment_start.tera index a335555992..e04543b111 100644 --- a/templates/mail_enrollment_start.tera +++ b/templates/mail_enrollment_start.tera @@ -9,7 +9,7 @@ token -> enrollment token {% block mail_content %} {% set client_docs_url="https://defguard.gitbook.io/defguard/features/desktop-client" %} {% set client_docs_link=macros::link(content=client_docs_url, href=client_docs_url) %} -{% set release_url="https://github.com/DefGuard/client/releases/latest" %} +{% set release_url="https://defguard.net/download/" %} {% set release_link=macros::link(content=release_url, href=release_url) %} {# intro #} {% set section_content = [ @@ -21,9 +21,10 @@ macros::paragraph(content="In order to start the enrollment process please choos {% set enrollment_link=macros::link(content=enrollment_url, href=enrollment_url) %} {% set section_content = [ macros::paragraph(content="1. Enrollment by desktop client"), -macros::paragraph(content="Download the official defguard desktop client for macOS or Linux from the release page: " ~ release_link), +macros::paragraph(content="Download the official defguard desktop client for Windows, macOS or Linux: " ~ release_link), macros::paragraph(content="After installation, please add a defguard instance by entering:"), macros::paragraph(content="
  • Instance URL: " ~ enrollment_link ~ "
  • Enrollment token: " ~ token ~ "
"), +macros::paragraph(content="Please note that: the token is only valid for 24 hours after receiving this email. When the enrollment process starts user will have 10 minutes to complete the process."), macros::paragraph(content="For more details go to the desktop client documentation: " ~ client_docs_link), ] %} {{ macros::text_section(content_array=section_content)}} @@ -35,7 +36,8 @@ macros::paragraph(content="If you choose this option, you will be able to change Desktop client can still be activated later, by accessing your profile in defguard: " ~ defguard_link ~ "."), macros::paragraph(content= "If you wish to do enrollment via Web, please copy & paste the following URL in your browser: "), macros::link(content=link_url, href=link_url), -macros::paragraph(content="Or click the button below:"), +macros::paragraph(content="Please note that: this option is only valid for 24 hours after receiving this email. When the enrollment process starts user will have 10 minutes to complete the process."), +macros::paragraph(content="You can also click the button below to start the enrollment:"), ] %} {{ macros::text_section(content_array=section_content)}}

TestClient { let (client, client_state) = make_test_client().await; - let mut wallet = Wallet::new_for_user( - client_state.test_user.id.unwrap(), + Wallet::new_for_user( + client_state.test_user.id, "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", "test", 5, "", - ); - wallet.save(&client_state.pool).await.unwrap(); + ) + .save(&client_state.pool) + .await + .unwrap(); client } -async fn make_client_with_db() -> (TestClient, DbPool) { +async fn make_client_with_db() -> (TestClient, PgPool) { let (client, client_state) = make_test_client().await; - let mut wallet = Wallet::new_for_user( - client_state.test_user.id.unwrap(), + Wallet::new_for_user( + client_state.test_user.id, "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", "test", 5, "", - ); - wallet.save(&client_state.pool).await.unwrap(); + ) + .save(&client_state.pool) + .await + .unwrap(); (client, client_state.pool) } @@ -66,14 +68,16 @@ async fn make_client_with_db() -> (TestClient, DbPool) { async fn make_client_with_state() -> (TestClient, ClientState) { let (client, client_state) = make_test_client().await; - let mut wallet = Wallet::new_for_user( - client_state.test_user.id.unwrap(), + Wallet::new_for_user( + client_state.test_user.id, "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", "test", 5, "", - ); - wallet.save(&client_state.pool).await.unwrap(); + ) + .save(&client_state.pool) + .await + .unwrap(); (client, client_state) } @@ -81,9 +85,10 @@ async fn make_client_with_state() -> (TestClient, ClientState) { async fn make_client_with_wallet(address: &str) -> TestClient { let (client, client_state) = make_test_client().await; - let mut wallet = - Wallet::new_for_user(client_state.test_user.id.unwrap(), address, "test", 5, ""); - wallet.save(&client_state.pool).await.unwrap(); + Wallet::new_for_user(client_state.test_user.id, address, "test", 5, "") + .save(&client_state.pool) + .await + .unwrap(); client } @@ -188,12 +193,21 @@ async fn test_cannot_enable_mfa() { } fn totp_code(auth_totp: &AuthTotp) -> AuthCode { - let auth = TOTP::from_base32(auth_totp.secret.clone()).unwrap(); let timestamp = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - AuthCode::new(auth.generate(TOTP_CODE_VALIDITY_PERIOD, timestamp)) + .unwrap(); + let secret = base32::decode( + base32::Alphabet::Rfc4648 { padding: false }, + &auth_totp.secret, + ) + .unwrap(); + let code = totp_custom::( + TOTP_CODE_VALIDITY_PERIOD, + TOTP_CODE_DIGITS, + &secret, + timestamp.as_secs(), + ); + AuthCode::new(code) } #[tokio::test] @@ -232,7 +246,7 @@ async fn test_totp() { assert_eq!(response.status(), StatusCode::UNAUTHORIZED); // provide wrong TOTP code - let code = AuthCode::new(0); + let code = AuthCode::new("0"); let response = client .post("/api/v1/auth/totp/verify") .json(&code) @@ -306,10 +320,10 @@ async fn test_totp() { } static EMAIL_CODE_REGEX: &str = r"(?\d{6})"; -fn extract_email_code(content: &str) -> u32 { +fn extract_email_code(content: &str) -> &str { let re = regex::Regex::new(EMAIL_CODE_REGEX).unwrap(); let code = re.captures(content).unwrap().name("code").unwrap().as_str(); - code.parse().unwrap() + code } #[tokio::test] @@ -390,7 +404,7 @@ async fn test_email_mfa() { assert_eq!(response.status(), StatusCode::UNAUTHORIZED); // provide wrong code - let code = AuthCode::new(0); + let code = AuthCode::new("0"); let response = client .post("/api/v1/auth/email/verify") .json(&code) diff --git a/tests/common/mod.rs b/tests/common/mod.rs index df003efbe8..394bedce57 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -6,7 +6,8 @@ use defguard::{ auth::failed_login::FailedLoginMap, build_webapp, config::DefGuardConfig, - db::{init_db, AppEvent, DbPool, GatewayEvent, User, UserDetails}, + db::{init_db, AppEvent, GatewayEvent, Id, User, UserDetails}, + enterprise::license::{set_cached_license, License}, grpc::{GatewayMap, WorkerState}, headers::create_user_agent_parser, mail::Mail, @@ -14,7 +15,7 @@ use defguard::{ }; use reqwest::{header::HeaderName, StatusCode}; use secrecy::ExposeSecret; -use sqlx::{postgres::PgConnectOptions, query, types::Uuid}; +use sqlx::{postgres::PgConnectOptions, query, types::Uuid, PgPool}; use tokio::sync::{ broadcast::{self, Receiver}, mpsc::{unbounded_channel, UnboundedReceiver}, @@ -29,7 +30,7 @@ pub const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for #[allow(dead_code, clippy::declare_interior_mutable_const)] pub const X_FORWARDED_URI: HeaderName = HeaderName::from_static("x-forwarded-uri"); -pub async fn init_test_db() -> (DbPool, DefGuardConfig) { +pub async fn init_test_db() -> (PgPool, DefGuardConfig) { let config = DefGuardConfig::new_test_config(); let _ = SERVER_CONFIG.set(config.clone()); let opts = PgConnectOptions::new() @@ -38,7 +39,7 @@ pub async fn init_test_db() -> (DbPool, DefGuardConfig) { .username(&config.database_user) .password(config.database_password.expose_secret()) .database(&config.database_name); - let pool = DbPool::connect_with(opts) + let pool = PgPool::connect_with(opts) .await .expect("Failed to connect to Postgres"); let db_name = Uuid::new_v4().to_string(); @@ -55,45 +56,47 @@ pub async fn init_test_db() -> (DbPool, DefGuardConfig) { ) .await; - initialize_users(&pool, config.clone()).await; + initialize_users(&pool, &config).await; (pool, config) } -async fn initialize_users(pool: &DbPool, config: DefGuardConfig) { +async fn initialize_users(pool: &PgPool, config: &DefGuardConfig) { User::init_admin_user(pool, config.default_admin_password.expose_secret()) .await .unwrap(); - let mut test_user = User::new( + User::new( "hpotter", Some("pass123"), "Potter", "Harry", "h.potter@hogwart.edu.uk", None, - ); - test_user.save(pool).await.unwrap(); + ) + .save(pool) + .await + .unwrap(); } pub struct ClientState { - pub pool: DbPool, + pub pool: PgPool, pub worker_state: Arc>, pub wireguard_rx: Receiver, pub mail_rx: UnboundedReceiver, pub failed_logins: Arc>, - pub test_user: User, + pub test_user: User, pub config: DefGuardConfig, } impl ClientState { pub fn new( - pool: DbPool, + pool: PgPool, worker_state: Arc>, wireguard_rx: Receiver, mail_rx: UnboundedReceiver, failed_logins: Arc>, - test_user: User, + test_user: User, config: DefGuardConfig, ) -> Self { Self { @@ -108,7 +111,7 @@ impl ClientState { } } -pub async fn make_base_client(pool: DbPool, config: DefGuardConfig) -> (TestClient, ClientState) { +pub async fn make_base_client(pool: PgPool, config: DefGuardConfig) -> (TestClient, ClientState) { let (tx, rx) = unbounded_channel::(); let worker_state = Arc::new(Mutex::new(WorkerState::new(tx.clone()))); let (wg_tx, wg_rx) = broadcast::channel::(16); @@ -120,6 +123,16 @@ pub async fn make_base_client(pool: DbPool, config: DefGuardConfig) -> (TestClie let user_agent_parser = create_user_agent_parser(); + let license = License::new( + "test_customer".to_string(), + true, + // Some(Utc.with_ymd_and_hms(2030, 1, 1, 0, 0, 0).unwrap()), + // Permanent license + None, + ); + + set_cached_license(Some(license)); + let client_state = ClientState::new( pool.clone(), worker_state.clone(), @@ -155,6 +168,7 @@ pub async fn make_base_client(pool: DbPool, config: DefGuardConfig) -> (TestClie user_agent_parser, failed_logins, ); + (TestClient::new(webapp).await, client_state) } @@ -166,7 +180,7 @@ pub async fn make_test_client() -> (TestClient, ClientState) { #[allow(dead_code)] pub async fn fetch_user_details(client: &TestClient, username: &str) -> UserDetails { - let response = client.get(&format!("/api/v1/user/{username}")).send().await; + let response = client.get(format!("/api/v1/user/{username}")).send().await; assert_eq!(response.status(), StatusCode::OK); response.json().await } diff --git a/tests/enrollment.rs b/tests/enrollment.rs index ec34283d53..63fe4112d0 100644 --- a/tests/enrollment.rs +++ b/tests/enrollment.rs @@ -2,16 +2,17 @@ mod common; use common::fetch_user_details; use defguard::{ - db::{models::enrollment::Token, DbPool}, + db::models::enrollment::Token, handlers::{AddUserData, Auth}, }; use reqwest::StatusCode; use serde::Deserialize; use serde_json::json; +use sqlx::PgPool; use self::common::{client::TestClient, make_test_client}; -async fn make_client() -> (TestClient, DbPool) { +async fn make_client() -> (TestClient, PgPool) { let (client, client_state) = make_test_client().await; (client, client_state.pool) } @@ -57,7 +58,7 @@ async fn test_initialize_enrollment() { username: "adumbledore2".into(), last_name: "Dumbledore".into(), first_name: "Albus".into(), - email: "a.dumbledore@hogwart.edu.uk".into(), + email: "a.dumbledore2@hogwart.edu.uk".into(), phone: Some("1234".into()), password: None, }; diff --git a/tests/enterprise_settings.rs b/tests/enterprise_settings.rs new file mode 100644 index 0000000000..66d63f5c3c --- /dev/null +++ b/tests/enterprise_settings.rs @@ -0,0 +1,222 @@ +mod common; + +use defguard::{ + enterprise::{ + db::models::enterprise_settings::EnterpriseSettings, + license::{get_cached_license, set_cached_license}, + }, + handlers::Auth, +}; +use reqwest::StatusCode; +use serde_json::{json, Value}; + +use self::common::make_test_client; + +fn make_network() -> Value { + json!({ + "name": "network", + "address": "10.1.1.1/24", + "port": 55555, + "endpoint": "192.168.4.14", + "allowed_ips": "10.1.1.0/24", + "dns": "1.1.1.1", + "allowed_groups": [], + "mfa_enabled": false, + "keepalive_interval": 25, + "peer_disconnect_threshold": 180 + }) +} + +#[tokio::test] +async fn test_only_enterprise_can_modify() { + // admin login + let (client, _client_state) = make_test_client().await; + let auth = Auth::new("admin", "pass123"); + let response = client.post("/api/v1/auth").json(&auth).send().await; + assert_eq!(response.status(), StatusCode::OK); + + // unset the license + let license = get_cached_license().clone(); + set_cached_license(None); + + // try to patch enterprise settings + let settings = EnterpriseSettings { + admin_device_management: true, + disable_all_traffic: false, + only_client_activation: false, + }; + + let response = client + .patch("/api/v1/settings_enterprise") + .json(&settings) + .send() + .await; + + // server should say nono + assert_eq!(response.status(), StatusCode::FORBIDDEN); + + // restore valid license and try again + set_cached_license(license); + let response = client + .patch("/api/v1/settings_enterprise") + .json(&settings) + .send() + .await; + + // server should say ok + assert_eq!(response.status(), StatusCode::OK); +} + +#[tokio::test] +async fn test_admin_devices_management_is_enforced() { + // admin login + let (client, _) = make_test_client().await; + let auth = Auth::new("admin", "pass123"); + let response = client.post("/api/v1/auth").json(&auth).send().await; + assert_eq!(response.status(), StatusCode::OK); + + // create network + let response = client + .post("/api/v1/network") + .json(&make_network()) + .send() + .await; + assert_eq!(response.status(), StatusCode::CREATED); + + // setup admin devices management + let settings = EnterpriseSettings { + admin_device_management: true, + disable_all_traffic: false, + only_client_activation: false, + }; + let response = client + .patch("/api/v1/settings_enterprise") + .json(&settings) + .send() + .await; + assert_eq!(response.status(), StatusCode::OK); + + // make sure admin can still manage devices + let device = json!({ + "name": "device", + "wireguard_pubkey": "LQKsT6/3HWKuJmMulH63R8iK+5sI8FyYEL6WDIi6lQU=", + }); + let response = client + .post("/api/v1/device/hpotter") + .json(&device) + .send() + .await; + assert_eq!(response.status(), StatusCode::CREATED); + + // ensure normal users can't manage devices + let auth = Auth::new("hpotter", "pass123"); + let response = client.post("/api/v1/auth").json(&auth).send().await; + assert_eq!(response.status(), StatusCode::OK); + + // add + let device = json!({ + "name": "userdevice", + "wireguard_pubkey": "AJwxGkzvVVn5Q1xjpCDFo5RJSU9KOPHeoEixYaj+20M=", + }); + let response = client + .post("/api/v1/device/hpotter") + .json(&device) + .send() + .await; + assert_eq!(response.status(), StatusCode::FORBIDDEN); + + // modify + let device = json!({ + "name": "modifieddevice", + "wireguard_pubkey": "AJwxGkzvVVn5Q1xjpCDFo5RJSU9KOPHeoEixYaj+20M=", + }); + let response = client.put("/api/v1/device/2").json(&device).send().await; + + assert_eq!(response.status(), StatusCode::FORBIDDEN); + + // delete + let device = json!({ + "name": "modifieddevice", + "wireguard_pubkey": "AJwxGkzvVVn5Q1xjpCDFo5RJSU9KOPHeoEixYaj+20M=", + }); + let response = client.put("/api/v1/device/2").json(&device).send().await; + + assert_eq!(response.status(), StatusCode::FORBIDDEN); +} + +#[tokio::test] +async fn test_regular_user_device_management() { + // admin login + let (client, _) = make_test_client().await; + let auth = Auth::new("admin", "pass123"); + let response = client.post("/api/v1/auth").json(&auth).send().await; + assert_eq!(response.status(), StatusCode::OK); + + // create network + let response = client + .post("/api/v1/network") + .json(&make_network()) + .send() + .await; + assert_eq!(response.status(), StatusCode::CREATED); + + // setup admin devices management + let settings = EnterpriseSettings { + admin_device_management: false, + disable_all_traffic: false, + only_client_activation: false, + }; + let response = client + .patch("/api/v1/settings_enterprise") + .json(&settings) + .send() + .await; + assert_eq!(response.status(), StatusCode::OK); + + // make sure admin can manage devices + let device = json!({ + "name": "device", + "wireguard_pubkey": "LQKsT6/3HWKuJmMulH63R8iK+5sI8FyYEL6WDIi6lQU=", + }); + let response = client + .post("/api/v1/device/hpotter") + .json(&device) + .send() + .await; + assert_eq!(response.status(), StatusCode::CREATED); + + // ensure normal users can manage devices + let auth = Auth::new("hpotter", "pass123"); + let response = client.post("/api/v1/auth").json(&auth).send().await; + assert_eq!(response.status(), StatusCode::OK); + + // add + let device = json!({ + "name": "userdevice", + "wireguard_pubkey": "AJwxGkzvVVn5Q1xjpCDFo5RJSU9KOPHeoEixYaj+20M=", + }); + let response = client + .post("/api/v1/device/hpotter") + .json(&device) + .send() + .await; + assert_eq!(response.status(), StatusCode::CREATED); + + // modify + let device = json!({ + "name": "modifieddevice", + "wireguard_pubkey": "AJwxGkzvVVn5Q1xjpCDFo5RJSU9KOPHeoEixYaj+20M=", + }); + let response = client.put("/api/v1/device/2").json(&device).send().await; + + assert_eq!(response.status(), StatusCode::OK); + + // delete + let device = json!({ + "name": "modifieddevice", + "wireguard_pubkey": "AJwxGkzvVVn5Q1xjpCDFo5RJSU9KOPHeoEixYaj+20M=", + }); + let response = client.put("/api/v1/device/2").json(&device).send().await; + + assert_eq!(response.status(), StatusCode::OK); +} diff --git a/tests/forward_auth.rs b/tests/forward_auth.rs index 9b939b354f..f5edd5e8c2 100644 --- a/tests/forward_auth.rs +++ b/tests/forward_auth.rs @@ -8,14 +8,16 @@ use self::common::{client::TestClient, make_test_client, X_FORWARDED_HOST, X_FOR async fn make_client() -> TestClient { let (client, client_state) = make_test_client().await; - let mut wallet = Wallet::new_for_user( - client_state.test_user.id.unwrap(), + Wallet::new_for_user( + client_state.test_user.id, "0x4aF8803CBAD86BA65ED347a3fbB3fb50e96eDD3e", "test", 5, "", - ); - wallet.save(&client_state.pool).await.unwrap(); + ) + .save(&client_state.pool) + .await + .unwrap(); client } diff --git a/tests/oauth.rs b/tests/oauth.rs index aebe081d1c..bbe5ac5b36 100644 --- a/tests/oauth.rs +++ b/tests/oauth.rs @@ -8,16 +8,17 @@ use defguard::{ oauth2client::{OAuth2Client, OAuth2ClientSafe}, NewOpenIDClient, }, - DbPool, OAuth2AuthorizedApp, + Id, OAuth2AuthorizedApp, }, handlers::Auth, }; use reqwest::{header::CONTENT_TYPE, StatusCode, Url}; use serde_json::json; +use sqlx::PgPool; use self::common::{client::TestClient, make_test_client}; -async fn make_client() -> (TestClient, DbPool) { +async fn make_client() -> (TestClient, PgPool) { let (client, client_state) = make_test_client().await; (client, client_state.pool) } @@ -43,11 +44,13 @@ async fn test_authorize() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let oauth_client: OAuth2Client = response.json().await; + let oauth_client: OAuth2Client = response.json().await; // authorize client for test user - let mut app = OAuth2AuthorizedApp::new(1, oauth_client.id.unwrap()); - app.save(&pool).await.unwrap(); + OAuth2AuthorizedApp::new(1, oauth_client.id) + .save(&pool) + .await + .unwrap(); // wrong response type let response = client @@ -186,7 +189,7 @@ async fn test_openid_app_management_access() { // list apps let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let apps: Vec = response.json().await; + let apps: Vec> = response.json().await; assert_eq!(apps.len(), 1); let test_app = &apps[0]; assert_eq!(test_app.name, oauth2client.name); @@ -197,7 +200,7 @@ async fn test_openid_app_management_access() { .send() .await; assert_eq!(response.status(), StatusCode::OK); - let app: OAuth2Client = response.json().await; + let app: OAuth2Client = response.json().await; assert_eq!(app.name, oauth2client.name); // edit app @@ -231,7 +234,7 @@ async fn test_openid_app_management_access() { .send() .await; assert_eq!(response.status(), StatusCode::OK); - let app: OAuth2Client = response.json().await; + let app: OAuth2Client = response.json().await; assert_eq!(app.name, oauth2client.name); assert!(!app.enabled); @@ -245,7 +248,7 @@ async fn test_openid_app_management_access() { // list apps let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let apps: Vec = response.json().await; + let apps: Vec> = response.json().await; assert_eq!(apps.len(), 0); // add another app for further testing @@ -263,7 +266,7 @@ async fn test_openid_app_management_access() { assert_eq!(response.status(), StatusCode::CREATED); let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let apps: Vec = response.json().await; + let apps: Vec> = response.json().await; let test_app = &apps[0]; // // login as standard user diff --git a/tests/openid.rs b/tests/openid.rs index 2605766228..2394335c91 100644 --- a/tests/openid.rs +++ b/tests/openid.rs @@ -6,7 +6,7 @@ use defguard::{ config::DefGuardConfig, db::{ models::{oauth2client::OAuth2Client, NewOpenIDClient}, - DbPool, + Id, }, handlers::Auth, }; @@ -25,6 +25,7 @@ use reqwest::{ }; use rsa::RsaPrivateKey; use serde::Deserialize; +use sqlx::PgPool; mod common; use self::common::{client::TestClient, init_test_db, make_base_client, make_test_client}; @@ -34,7 +35,7 @@ async fn make_client() -> TestClient { client } -async fn make_client_v2(pool: DbPool, config: DefGuardConfig) -> TestClient { +async fn make_client_v2(pool: PgPool, config: DefGuardConfig) -> TestClient { let (client, _) = make_base_client(pool, config).await; client } @@ -69,7 +70,7 @@ async fn test_openid_client() { let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let openid_clients: Vec = response.json().await; + let openid_clients: Vec> = response.json().await; assert_eq!(openid_clients.len(), 1); openid_client.name = "Test changed".into(); @@ -85,7 +86,7 @@ async fn test_openid_client() { .send() .await; assert_eq!(response.status(), StatusCode::OK); - let fetched_client: OAuth2Client = response.json().await; + let fetched_client: OAuth2Client = response.json().await; assert_eq!(fetched_client.name, openid_client.name); // OpenID flow tests @@ -100,7 +101,7 @@ async fn test_openid_client() { let response = client.get("/api/v1/oauth").send().await; assert_eq!(response.status(), StatusCode::OK); - let openid_clients: Vec = response.json().await; + let openid_clients: Vec> = response.json().await; assert!(openid_clients.is_empty()); } @@ -123,7 +124,7 @@ async fn test_openid_flow() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let openid_client: OAuth2Client = response.json().await; + let openid_client: OAuth2Client = response.json().await; assert_eq!(openid_client.name, "Test"); // all clients @@ -360,7 +361,7 @@ async fn test_openid_flow() { /// Helper function for translating HTTP communication from `HttpRequest` to `LocalClient`. async fn http_client( request: HttpRequest, - pool: DbPool, + pool: PgPool, config: DefGuardConfig, ) -> Result { let client = make_client_v2(pool, config).await; @@ -429,7 +430,7 @@ async fn test_openid_authorization_code() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let oauth2client: OAuth2Client = response.json().await; + let oauth2client: OAuth2Client = response.json().await; assert_eq!(oauth2client.name, "My test client"); assert_eq!(oauth2client.scope[0], "openid"); assert_eq!(oauth2client.client_id.len(), 16); @@ -534,7 +535,7 @@ async fn test_openid_authorization_code_with_pkce() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let oauth2client: OAuth2Client = response.json().await; + let oauth2client: OAuth2Client = response.json().await; assert_eq!(oauth2client.name, "My test client"); assert_eq!(oauth2client.scope[0], "openid"); @@ -643,7 +644,7 @@ async fn test_openid_flow_new_login_mail() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let openid_client: OAuth2Client = response.json().await; + let openid_client: OAuth2Client = response.json().await; assert_eq!(openid_client.name, "Test"); // all clients diff --git a/tests/openid_login.rs b/tests/openid_login.rs new file mode 100644 index 0000000000..beb62c7e54 --- /dev/null +++ b/tests/openid_login.rs @@ -0,0 +1,86 @@ +use chrono::{Duration, Utc}; +use defguard::{ + config::DefGuardConfig, + enterprise::{ + handlers::openid_providers::AddProviderData, + license::{set_cached_license, License}, + }, + handlers::Auth, +}; +use reqwest::{StatusCode, Url}; +use serde::Deserialize; +use sqlx::PgPool; + +mod common; +use self::common::{client::TestClient, make_base_client, make_test_client}; + +async fn make_client() -> TestClient { + let (client, _) = make_test_client().await; + client +} + +#[allow(dead_code)] +async fn make_client_v2(pool: PgPool, config: DefGuardConfig) -> TestClient { + let (client, _) = make_base_client(pool, config).await; + client +} + +#[tokio::test] +async fn test_openid_providers() { + let client = make_client().await; + + let auth = Auth::new("admin", "pass123"); + let response = client.post("/api/v1/auth").json(&auth).send().await; + assert_eq!(response.status(), StatusCode::OK); + + let provider_data = AddProviderData::new( + "test", + "https://accounts.google.com", + "client_id", + "client_secret", + ); + + let response = client + .post("/api/v1/openid/provider") + .json(&provider_data) + .send() + .await; + + assert_eq!(response.status(), StatusCode::CREATED); + + let response = client.get("/api/v1/openid/auth_info").send().await; + + assert_eq!(response.status(), StatusCode::OK); + + #[derive(Deserialize)] + struct UrlResponse { + url: String, + } + + let provider: UrlResponse = response.json::().await; + + let url = Url::parse(&provider.url).unwrap(); + + let client_id = url + .query_pairs() + .find(|(key, _)| key == "client_id") + .unwrap(); + assert_eq!(client_id.1, "client_id"); + + let nonce = url.query_pairs().find(|(key, _)| key == "nonce"); + assert!(nonce.is_some()); + let state = url.query_pairs().find(|(key, _)| key == "state"); + assert!(state.is_some()); + let redirect_uri = url.query_pairs().find(|(key, _)| key == "redirect_uri"); + assert!(redirect_uri.is_some()); + + // Test that the endpoint is forbidden when the license is expired + let new_license = License { + customer_id: "test".to_string(), + subscription: false, + valid_until: Some(Utc::now() - Duration::days(1)), + }; + set_cached_license(Some(new_license)); + let response = client.get("/api/v1/openid/auth_info").send().await; + assert_eq!(response.status(), StatusCode::FORBIDDEN); +} diff --git a/tests/user.rs b/tests/user.rs index 24845cd0ff..66d6730b23 100644 --- a/tests/user.rs +++ b/tests/user.rs @@ -3,7 +3,7 @@ mod common; use defguard::{ db::{ models::{oauth2client::OAuth2Client, wallet::keccak256, NewOpenIDClient}, - AddDevice, UserInfo, + AddDevice, Id, UserInfo, }, handlers::{AddUserData, Auth, PasswordChange, PasswordChangeSelf, Username, WalletChallenge}, hex::to_lower_hex, @@ -434,12 +434,12 @@ async fn test_check_username() { let invalid_usernames = ["ADumble dore", ".1user"]; let valid_usernames = ["user1", "use2r3", "not_wrong"]; - for username in invalid_usernames { + for (i, username) in invalid_usernames.into_iter().enumerate() { let new_user = AddUserData { username: username.into(), last_name: "Dumbledore".into(), first_name: "Albus".into(), - email: "a.dumbledore@hogwart.edu.uk".into(), + email: format!("a.dumbledore{i}@hogwart.edu.uk"), phone: Some("1234".into()), password: Some("Alohomora!12".into()), }; @@ -447,12 +447,12 @@ async fn test_check_username() { assert_eq!(response.status(), StatusCode::BAD_REQUEST); } - for username in valid_usernames { + for (i, username) in valid_usernames.into_iter().enumerate() { let new_user = AddUserData { username: username.into(), last_name: "Dumbledore".into(), first_name: "Albus".into(), - email: "a.dumbledore@hogwart.edu.uk".into(), + email: format!("a.dumbledore{i}@hogwart.edu.uk"), phone: Some("1234".into()), password: Some("Alohomora!12".into()), }; @@ -528,7 +528,7 @@ async fn test_user_unregister_authorized_app() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let openid_client: OAuth2Client = response.json().await; + let openid_client: OAuth2Client = response.json().await; assert_eq!(openid_client.name, "Test"); let response = client .post(format!( @@ -763,3 +763,36 @@ async fn test_disable() { .await; assert_eq!(response.status(), StatusCode::OK); } + +#[tokio::test] +async fn test_unique_email() { + let client = make_client().await; + + let auth = Auth::new("admin", "pass123"); + let response = client.post("/api/v1/auth").json(&auth).send().await; + assert_eq!(response.status(), StatusCode::OK); + + // create user + let new_user = AddUserData { + username: "adumbledore".into(), + last_name: "Dumbledore".into(), + first_name: "Albus".into(), + email: "a.dumbledore@hogwart.edu.uk".into(), + phone: Some("1234".into()), + password: Some("Password1234543$!".into()), + }; + let response = client.post("/api/v1/user").json(&new_user).send().await; + assert_eq!(response.status(), StatusCode::CREATED); + + // create user with same email + let new_user = AddUserData { + username: "adumbledore2".into(), + last_name: "Dumbledore".into(), + first_name: "Albus".into(), + email: "a.dumbledore@hogwart.edu.uk".into(), + phone: Some("1234".into()), + password: Some("Password1234543$!".into()), + }; + let response = client.post("/api/v1/user").json(&new_user).send().await; + assert_eq!(response.status(), StatusCode::BAD_REQUEST); +} diff --git a/tests/webhook.rs b/tests/webhook.rs index fe986d582a..42ec6876aa 100644 --- a/tests/webhook.rs +++ b/tests/webhook.rs @@ -1,6 +1,9 @@ mod common; -use defguard::{db::WebHook, handlers::Auth}; +use defguard::{ + db::{Id, NoId, WebHook}, + handlers::Auth, +}; use reqwest::StatusCode; use self::common::{client::TestClient, make_test_client}; @@ -19,7 +22,7 @@ async fn test_webhooks() { assert_eq!(response.status(), StatusCode::OK); let mut webhook = WebHook { - id: None, + id: NoId, url: "http://localhost:3000/trigger-happy".into(), description: "Test".into(), token: "1234567890".into(), @@ -35,36 +38,36 @@ async fn test_webhooks() { let response = client.get("/api/v1/webhook").send().await; assert_eq!(response.status(), StatusCode::OK); - let webhooks: Vec = response.json().await; + let webhooks: Vec> = response.json().await; assert_eq!(webhooks.len(), 1); webhook.description = "Changed".into(); webhook.on_user_modified = false; let response = client - .put(format!("/api/v1/webhook/{}", webhooks[0].id.unwrap())) + .put(format!("/api/v1/webhook/{}", webhooks[0].id)) .json(&webhook) .send() .await; assert_eq!(response.status(), StatusCode::OK); let response = client - .get(format!("/api/v1/webhook/{}", webhooks[0].id.unwrap())) + .get(format!("/api/v1/webhook/{}", webhooks[0].id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); - let fetched_webhook: WebHook = response.json().await; + let fetched_webhook: WebHook = response.json().await; assert_eq!(fetched_webhook.url, webhook.url); assert_eq!(fetched_webhook.description, webhook.description); assert_eq!(fetched_webhook.on_user_modified, webhook.on_user_modified); let response = client - .delete(format!("/api/v1/webhook/{}", webhooks[0].id.unwrap())) + .delete(format!("/api/v1/webhook/{}", webhooks[0].id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); let response = client.get("/api/v1/webhook").send().await; assert_eq!(response.status(), StatusCode::OK); - let webhooks: Vec = response.json().await; + let webhooks: Vec> = response.json().await; assert!(webhooks.is_empty()); } diff --git a/tests/wireguard.rs b/tests/wireguard.rs index 5fbe1983cf..a3cd2814fe 100644 --- a/tests/wireguard.rs +++ b/tests/wireguard.rs @@ -6,7 +6,7 @@ use defguard::{ device::WireguardNetworkDevice, wireguard::{DEFAULT_DISCONNECT_THRESHOLD, DEFAULT_KEEPALIVE_INTERVAL}, }, - Device, GatewayEvent, WireguardNetwork, + Device, GatewayEvent, Id, WireguardNetwork, }, handlers::{wireguard::WireguardNetworkData, Auth, GroupInfo}, }; @@ -48,7 +48,7 @@ async fn test_network() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -72,7 +72,7 @@ async fn test_network() { peer_disconnect_threshold: DEFAULT_DISCONNECT_THRESHOLD, }; let response = client - .put(format!("/api/v1/network/{}", network.id.unwrap())) + .put(format!("/api/v1/network/{}", network.id)) .json(&network_data) .send() .await; @@ -89,23 +89,23 @@ async fn test_network() { // list networks let response = client.get("/api/v1/network").send().await; assert_eq!(response.status(), StatusCode::OK); - let networks: Vec = response.json().await; + let networks: Vec> = response.json().await; assert_eq!(networks.len(), 1); // network details let network_from_list = networks[0].clone(); assert_eq!(network_from_list.name, "my network"); let response = client - .get(format!("/api/v1/network/{}", network_from_list.id.unwrap())) + .get(format!("/api/v1/network/{}", network_from_list.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); - let network_from_details: WireguardNetwork = response.json().await; + let network_from_details: WireguardNetwork = response.json().await; assert_eq!(network_from_details, network_from_list); // delete network let response = client - .delete(format!("/api/v1/network/{}", network.id.unwrap())) + .delete(format!("/api/v1/network/{}", network.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); @@ -136,7 +136,7 @@ async fn test_device() { // network details let response = client.get("/api/v1/network/1").send().await; assert_eq!(response.status(), StatusCode::OK); - let network_from_details: WireguardNetwork = response.json().await; + let network_from_details: WireguardNetwork = response.json().await; // create device let device = json!({ @@ -159,7 +159,7 @@ async fn test_device() { .unwrap(); assert_eq!( network_devices[0].wireguard_network_id, - network_from_details.id.unwrap() + network_from_details.id ); // add another network @@ -181,7 +181,7 @@ async fn test_device() { // list devices let response = client.get("/api/v1/device").json(&device).send().await; assert_eq!(response.status(), StatusCode::OK); - let devices: Vec = response.json().await; + let devices: Vec> = response.json().await; assert_eq!(devices.len(), 1); let device = devices[0].clone(); assert_eq!(device.name, "device"); @@ -197,7 +197,7 @@ async fn test_device() { .send() .await; assert_eq!(response.status(), StatusCode::OK); - let user_devices: Vec = response.json().await; + let user_devices: Vec> = response.json().await; assert_eq!(user_devices.len(), 1); assert_eq!(devices.len(), 1); assert_eq!(device.id, user_devices[0].id); @@ -209,7 +209,7 @@ async fn test_device() { modified_device.name = modified_name.into(); modified_device.wireguard_pubkey = modified_key.into(); let response = client - .put(format!("/api/v1/device/{}", device.id.unwrap())) + .put(format!("/api/v1/device/{}", device.id)) .json(&modified_device) .send() .await; @@ -219,20 +219,17 @@ async fn test_device() { // device details let response = client - .get(format!("/api/v1/device/{}", device.id.unwrap())) + .get(format!("/api/v1/device/{}", device.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); - let device_from_details: Device = response.json().await; + let device_from_details: Device = response.json().await; assert_eq!(device_from_details.name, modified_name); assert_eq!(device_from_details.wireguard_pubkey, modified_key); // device config let response = client - .get(format!( - "/api/v1/network/1/device/{}/config", - device.id.unwrap() - )) + .get(format!("/api/v1/network/1/device/{}/config", device.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); @@ -255,10 +252,7 @@ async fn test_device() { ); let response = client - .delete(format!( - "/api/v1/network/{}", - network_from_details.id.unwrap() - )) + .delete(format!("/api/v1/network/{}", network_from_details.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); @@ -267,7 +261,7 @@ async fn test_device() { // delete device let response = client - .delete(format!("/api/v1/device/{}", device.id.unwrap())) + .delete(format!("/api/v1/device/{}", device.id)) .send() .await; assert_eq!(response.status(), StatusCode::OK); @@ -276,7 +270,7 @@ async fn test_device() { let response = client.get("/api/v1/device").json(&device).send().await; assert_eq!(response.status(), StatusCode::OK); - let devices: Vec = response.json().await; + let devices: Vec> = response.json().await; assert!(devices.is_empty()); } @@ -404,7 +398,7 @@ async fn test_device_permissions() { let response = client.get("/api/v1/device/user/hpotter").send().await; assert_eq!(response.status(), StatusCode::OK); - let user_devices: Vec = response.json().await; + let user_devices: Vec> = response.json().await; assert_eq!(user_devices.len(), 3); // admin can list devices of other users @@ -414,12 +408,12 @@ async fn test_device_permissions() { let response = client.get("/api/v1/device/user/admin").send().await; assert_eq!(response.status(), StatusCode::OK); - let user_devices: Vec = response.json().await; + let user_devices: Vec> = response.json().await; assert_eq!(user_devices.len(), 2); let response = client.get("/api/v1/device/user/hpotter").send().await; assert_eq!(response.status(), StatusCode::OK); - let user_devices: Vec = response.json().await; + let user_devices: Vec> = response.json().await; assert_eq!(user_devices.len(), 3); } @@ -446,7 +440,7 @@ async fn test_device_pubkey() { // network details let response = client.get("/api/v1/network/1").send().await; assert_eq!(response.status(), StatusCode::OK); - let network_from_details: WireguardNetwork = response.json().await; + let network_from_details: WireguardNetwork = response.json().await; // create bad device let device = json!({ @@ -487,14 +481,14 @@ async fn test_device_pubkey() { // list devices let response = client.get("/api/v1/device").json(&device).send().await; assert_eq!(response.status(), StatusCode::OK); - let devices: Vec = response.json().await; + let devices: Vec> = response.json().await; assert_eq!(devices.len(), 1); // modify device let mut device = devices[0].clone(); device.wireguard_pubkey = network_from_details.pubkey; let response = client - .put(format!("/api/v1/device/{}", device.id.unwrap())) + .put(format!("/api/v1/device/{}", device.id)) .json(&device) .send() .await; @@ -525,6 +519,6 @@ async fn test_device_pubkey() { // make sure no device was created let response = client.get("/api/v1/device").json(&device).send().await; assert_eq!(response.status(), StatusCode::OK); - let devices: Vec = response.json().await; + let devices: Vec> = response.json().await; assert_eq!(devices.len(), 1); } diff --git a/tests/wireguard_network_allowed_groups.rs b/tests/wireguard_network_allowed_groups.rs index 484e612a48..c5e93e4718 100644 --- a/tests/wireguard_network_allowed_groups.rs +++ b/tests/wireguard_network_allowed_groups.rs @@ -2,37 +2,38 @@ mod common; use claims::assert_err; use defguard::{ - db::{DbPool, Device, GatewayEvent, Group, User, WireguardNetwork}, + db::{Device, GatewayEvent, Group, Id, User, WireguardNetwork}, handlers::{wireguard::ImportedNetworkData, Auth}, }; use matches::assert_matches; use reqwest::StatusCode; use serde_json::json; +use sqlx::PgPool; use self::common::{fetch_user_details, make_test_client}; // setup user groups, test users and devices -async fn setup_test_users(pool: &DbPool) -> (Vec, Vec) { +async fn setup_test_users(pool: &PgPool) -> (Vec>, Vec>) { let mut users = Vec::new(); let mut devices = Vec::new(); // create user groups - let mut allowed_group = Group::new("allowed group"); - allowed_group.save(pool).await.unwrap(); + let allowed_group = Group::new("allowed group").save(pool).await.unwrap(); - let mut not_allowed_group = Group::new("not allowed group"); - not_allowed_group.save(pool).await.unwrap(); + let not_allowed_group = Group::new("not allowed group").save(pool).await.unwrap(); // admin user let admin_user = User::find_by_username(pool, "admin") .await .unwrap() .unwrap(); - let mut admin_device = Device::new( + let admin_device = Device::new( "admin device".into(), "nst4lmZz9kPTq6OdeQq2G2th3n+QneHKmG1wJJ3Jrq0=".into(), - admin_user.id.unwrap(), - ); - admin_device.save(pool).await.unwrap(); + admin_user.id, + ) + .save(pool) + .await + .unwrap(); users.push(admin_user); devices.push(admin_device); @@ -42,54 +43,64 @@ async fn setup_test_users(pool: &DbPool) -> (Vec, Vec) { .unwrap() .unwrap(); test_user.add_to_group(pool, &allowed_group).await.unwrap(); - let mut test_device = Device::new( + let test_device = Device::new( "test device".into(), "wYOt6ImBaQ3BEMQ3Xf5P5fTnbqwOvjcqYkkSBt+1xOg=".into(), - test_user.id.unwrap(), - ); - test_device.save(pool).await.unwrap(); + test_user.id, + ) + .save(pool) + .await + .unwrap(); users.push(test_user); devices.push(test_device); // standard user in other, non-allowed group - let mut other_user = User::new( + let other_user = User::new( "ssnape", Some("pass123"), "Snape", "Severus", "s.snape@hogwart.edu.uk", None, - ); - other_user.save(pool).await.unwrap(); + ) + .save(pool) + .await + .unwrap(); other_user .add_to_group(pool, ¬_allowed_group) .await .unwrap(); - let mut other_device = Device::new( + let other_device = Device::new( "other device".into(), "v2U14sjNN4tOYD3P15z0WkjriKY9Hl85I3vIEPomrYs=".into(), - other_user.id.unwrap(), - ); - other_device.save(pool).await.unwrap(); + other_user.id, + ) + .save(pool) + .await + .unwrap(); users.push(other_user); devices.push(other_device); // standard user in no groups - let mut non_group_user = User::new( + let non_group_user = User::new( "dobby", Some("pass123"), "Elf", "Dobby", "dobby@hogwart.edu.uk", None, - ); - non_group_user.save(pool).await.unwrap(); - let mut non_group_device = Device::new( + ) + .save(pool) + .await + .unwrap(); + let non_group_device = Device::new( "non group device".into(), "6xmL/jRuxmzQ3J2/kVZnKnh+6dwODcEEczmmkIKU4sM=".into(), - non_group_user.id.unwrap(), - ); - non_group_device.save(pool).await.unwrap(); + non_group_user.id, + ) + .save(pool) + .await + .unwrap(); users.push(non_group_user); devices.push(non_group_device); @@ -125,7 +136,7 @@ async fn test_create_new_network() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -167,7 +178,7 @@ async fn test_modify_network() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -353,7 +364,7 @@ async fn test_import_network_existing_devices() { let GatewayEvent::DeviceModified(device_info) = wg_rx.try_recv().unwrap() else { panic!() }; - assert_eq!(device_info.device.id.unwrap(), devices[1].id.unwrap()); + assert_eq!(device_info.device.id, devices[1].id); assert_eq!(device_info.network_info.len(), 1); assert_eq!(device_info.network_info[0].network_id, 1); assert_eq!( @@ -364,7 +375,7 @@ async fn test_import_network_existing_devices() { let GatewayEvent::DeviceCreated(device_info) = wg_rx.try_recv().unwrap() else { panic!() }; - assert_eq!(device_info.device.id.unwrap(), devices[0].id.unwrap()); + assert_eq!(device_info.device.id, devices[0].id); assert_eq!(device_info.network_info.len(), 1); assert_eq!(device_info.network_info[0].network_id, 1); assert_eq!( @@ -431,13 +442,13 @@ PersistentKeepalive = 300 } // assign devices to users - mapped_devices[0].user_id = users[0].id; - mapped_devices[1].user_id = users[1].id; - mapped_devices[2].user_id = users[2].id; - mapped_devices[3].user_id = users[3].id; + mapped_devices[0].user_id = Some(users[0].id); + mapped_devices[1].user_id = Some(users[1].id); + mapped_devices[2].user_id = Some(users[2].id); + mapped_devices[3].user_id = Some(users[3].id); let response = client - .post(format!("/api/v1/network/{}/devices", network.id.unwrap())) + .post(format!("/api/v1/network/{}/devices", network.id)) .json(&json!({"devices": mapped_devices.clone()})) .send() .await; @@ -512,7 +523,7 @@ async fn test_modify_user() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); @@ -607,7 +618,7 @@ async fn test_delete_only_allowed_group() { .send() .await; assert_eq!(response.status(), StatusCode::CREATED); - let network: WireguardNetwork = response.json().await; + let network: WireguardNetwork = response.json().await; assert_eq!(network.name, "network"); let event = wg_rx.try_recv().unwrap(); assert_matches!(event, GatewayEvent::NetworkCreated(..)); diff --git a/tests/wireguard_network_import.rs b/tests/wireguard_network_import.rs index 1ac5c5f3a1..d5e43656f4 100644 --- a/tests/wireguard_network_import.rs +++ b/tests/wireguard_network_import.rs @@ -45,7 +45,7 @@ async fn test_config_import() { let pool = client_state.pool; // setup initial network - let mut initial_network = WireguardNetwork::new( + let initial_network = WireguardNetwork::new( "initial".into(), "10.1.9.0/24".parse().unwrap(), 51515, @@ -62,23 +62,27 @@ async fn test_config_import() { // add existing devices let mut transaction = pool.begin().await.unwrap(); - let mut device_1 = Device::new( + let device_1 = Device::new( "test device".into(), "l07+qPWs4jzW3Gp1DKbHgBMRRm4Jg3q2BJxw0ZYl6c4=".into(), 1, - ); - device_1.save(&mut *transaction).await.unwrap(); + ) + .save(&mut *transaction) + .await + .unwrap(); device_1 .add_to_all_networks(&mut transaction) .await .unwrap(); - let mut device_2 = Device::new( + let device_2 = Device::new( "another test device".into(), "v2U14sjNN4tOYD3P15z0WkjriKY9Hl85I3vIEPomrYs=".into(), 1, - ); - device_2.save(&mut *transaction).await.unwrap(); + ) + .save(&mut *transaction) + .await + .unwrap(); device_2 .add_to_all_networks(&mut transaction) .await @@ -103,7 +107,7 @@ async fn test_config_import() { // network assertions let network = response.network; - assert_eq!(network.id, Some(2)); + assert_eq!(network.id, 2); assert_eq!(network.name, "network"); assert_eq!(network.address, "10.0.0.1/24".parse().unwrap()); assert_eq!(network.port, 55055); @@ -166,7 +170,7 @@ async fn test_config_import() { // post modified devices let response = client - .post(format!("/api/v1/network/{}/devices", network.id.unwrap())) + .post(format!("/api/v1/network/{}/devices", network.id)) .json(&json!({"devices": [device1, device2]})) .send() .await; diff --git a/tests/wireguard_network_stats.rs b/tests/wireguard_network_stats.rs index 8f73d049e4..11f5f7654e 100644 --- a/tests/wireguard_network_stats.rs +++ b/tests/wireguard_network_stats.rs @@ -6,7 +6,7 @@ use defguard::{ models::wireguard::{ WireguardDeviceTransferRow, WireguardNetworkStats, WireguardUserStatsRow, }, - Device, WireguardPeerStats, + Device, Id, NoId, WireguardPeerStats, }, handlers::Auth, }; @@ -15,6 +15,8 @@ use serde_json::{json, Value}; use self::common::make_test_client; +static DATE_FORMAT: &str = "%Y-%m-%dT%H:%M:00Z"; + fn make_network() -> Value { json!({ "name": "network", @@ -71,7 +73,7 @@ async fn test_stats() { assert_eq!(response.status(), StatusCode::CREATED); // get devices - let mut devices = Vec::::new(); + let mut devices = Vec::>::new(); let response = client.get("/api/v1/device/1").send().await; assert_eq!(response.status(), StatusCode::OK); devices.push(response.json().await); @@ -86,7 +88,7 @@ async fn test_stats() { let response = client .get(format!( "/api/v1/network/1/stats/users?from={}", - hour_ago.format("%Y-%m-%dT%H:%M:00Z"), + hour_ago.format(DATE_FORMAT), )) .send() .await; @@ -98,9 +100,9 @@ async fn test_stats() { let samples = 60 * 11; // 11 hours of samples for i in 0..samples { for (d, device) in devices.iter().enumerate().take(2) { - let mut wps = WireguardPeerStats { - id: None, - device_id: device.id.unwrap(), + WireguardPeerStats { + id: NoId, + device_id: device.id, collected_at: now - Duration::minutes(i), network: 1, endpoint: Some("11.22.33.44".into()), @@ -108,8 +110,10 @@ async fn test_stats() { download: (samples - i) * 20 * (d as i64 + 1), latest_handshake: now - Duration::minutes(i * 10), allowed_ips: Some("10.1.1.0/24".into()), - }; - wps.save(&pool).await.unwrap(); + } + .save(&pool) + .await + .unwrap(); } } @@ -117,7 +121,7 @@ async fn test_stats() { let response = client .get(format!( "/api/v1/network/1/stats/users?from={}", - hour_ago.format("%Y-%m-%dT%H:%M:00Z"), + hour_ago.format(DATE_FORMAT), )) .send() .await; @@ -221,7 +225,7 @@ async fn test_stats() { let response = client .get(format!( "/api/v1/network/1/stats/users?from={}", - ten_hours_ago.format("%Y-%m-%dT%H:%M:00Z"), + ten_hours_ago.format(DATE_FORMAT), )) .send() .await; @@ -252,7 +256,7 @@ async fn test_stats() { let response = client .get(format!( "/api/v1/network/1/stats?from={}", - ten_hours_ago.format("%Y-%m-%dT%H:%M:00Z"), + ten_hours_ago.format(DATE_FORMAT), )) .send() .await; diff --git a/user_agent_header_regexes.yaml b/user_agent_header_regexes.yaml index a8f8e09758..5609618af7 100644 --- a/user_agent_header_regexes.yaml +++ b/user_agent_header_regexes.yaml @@ -1,5863 +1,3875 @@ user_agent_parsers: - #### SPECIAL CASES TOP #### - - # ESRI Server products - - regex: '(GeoEvent Server) (\d+)(?:\.(\d+)(?:\.(\d+)|)|)' - - # ESRI ArcGIS Desktop Products + - regex: (GeoEvent Server) (\d+)(?:\.(\d+)(?:\.(\d+)|)|) - regex: '(ArcGIS Pro)(?: (\d+)\.(\d+)\.([^ ]+)|)' - - - regex: 'ArcGIS Client Using WinInet' - family_replacement: 'ArcMap' - - - regex: '(OperationsDashboard)-(?:Windows)-(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Operations Dashboard for ArcGIS' - - - regex: '(arcgisearth)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'ArcGIS Earth' - - - regex: 'com.esri.(earth).phone/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'ArcGIS Earth' - - # ESRI ArcGIS Mobile Products - - regex: '(arcgis-explorer)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Explorer for ArcGIS' - - - regex: 'arcgis-(collector|aurora)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Collector for ArcGIS' - - - regex: '(arcgis-workforce)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Workforce for ArcGIS' - - - regex: '(Collector|Explorer|Workforce)-(?:Android|iOS)-(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: '$1 for ArcGIS' - - - regex: '(Explorer|Collector)/(\d+) CFNetwork' - family_replacement: '$1 for ArcGIS' - - # ESRI ArcGIS Runtimes - - regex: 'ArcGISRuntime-(Android|iOS|NET|Qt)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'ArcGIS Runtime SDK for $1' - - - regex: 'ArcGIS\.?(iOS|Android|NET|Qt)(?:-|\.)(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'ArcGIS Runtime SDK for $1' - - - regex: 'ArcGIS\.Runtime\.(Qt)\.(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'ArcGIS Runtime SDK for $1' - - # CFNetwork Podcast catcher Applications - - regex: '^(Luminary)[Stage]+/(\d+) CFNetwork' - - regex: '(ESPN)[%20| ]+Radio/(\d+)\.(\d+)\.(\d+) CFNetwork' - - regex: '(Antenna)/(\d+) CFNetwork' - family_replacement: 'AntennaPod' - - regex: '(TopPodcasts)Pro/(\d+) CFNetwork' - - regex: '(MusicDownloader)Lite/(\d+)\.(\d+)\.(\d+) CFNetwork' - - regex: '^(.{0,200})-iPad\/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) CFNetwork' - - regex: '^(.{0,200})-iPhone/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) CFNetwork' - - regex: '^(.{0,200})/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) CFNetwork' - - # Podcast catchers - - regex: '^(Luminary)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - - regex: '(espn\.go)' - family_replacement: 'ESPN' - - regex: '(espnradio\.com)' - family_replacement: 'ESPN' - - regex: 'ESPN APP$' - family_replacement: 'ESPN' - - regex: '(audioboom\.com)' - family_replacement: 'AudioBoom' + - regex: ArcGIS Client Using WinInet + family_replacement: ArcMap + - regex: (OperationsDashboard)-(?:Windows)-(\d+)\.(\d+)\.(\d+) + family_replacement: Operations Dashboard for ArcGIS + - regex: (arcgisearth)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: ArcGIS Earth + - regex: com.esri.(earth).phone/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: ArcGIS Earth + - regex: (arcgis-explorer)/(\d+)\.(\d+)\.(\d+) + family_replacement: Explorer for ArcGIS + - regex: arcgis-(collector|aurora)/(\d+)\.(\d+)\.(\d+) + family_replacement: Collector for ArcGIS + - regex: (arcgis-workforce)/(\d+)\.(\d+)\.(\d+) + family_replacement: Workforce for ArcGIS + - regex: (Collector|Explorer|Workforce)-(?:Android|iOS)-(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: $1 for ArcGIS + - regex: (Explorer|Collector)/(\d+) CFNetwork + family_replacement: $1 for ArcGIS + - regex: ArcGISRuntime-(Android|iOS|NET|Qt)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: ArcGIS Runtime SDK for $1 + - regex: ArcGIS\.?(iOS|Android|NET|Qt)(?:-|\.)(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: ArcGIS Runtime SDK for $1 + - regex: ArcGIS\.Runtime\.(Qt)\.(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: ArcGIS Runtime SDK for $1 + - regex: ^(Luminary)[Stage]+/(\d+) CFNetwork + - regex: (ESPN)[%20| ]+Radio/(\d+)\.(\d+)\.(\d+) CFNetwork + - regex: (Antenna)/(\d+) CFNetwork + family_replacement: AntennaPod + - regex: (TopPodcasts)Pro/(\d+) CFNetwork + - regex: (MusicDownloader)Lite/(\d+)\.(\d+)\.(\d+) CFNetwork + - regex: ^(.{0,200})-iPad\/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) CFNetwork + - regex: ^(.{0,200})-iPhone/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) CFNetwork + - regex: ^(.{0,200})/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) CFNetwork + - regex: ^(Luminary)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (espn\.go) + family_replacement: ESPN + - regex: (espnradio\.com) + family_replacement: ESPN + - regex: ESPN APP$ + family_replacement: ESPN + - regex: (audioboom\.com) + family_replacement: AudioBoom - regex: ' (Rivo) RHYTHM' - - # @note: iOS / OSX Applications - - regex: '(CFNetwork)(?:/(\d+)\.(\d+)(?:\.(\d+)|)|)' - family_replacement: 'CFNetwork' - - # Pingdom - - regex: '(Pingdom\.com_bot_version_)(\d+)\.(\d+)' - family_replacement: 'PingdomBot' - # 'Mozilla/5.0 (Unknown; Linux x86_64) AppleWebKit/534.34 (KHTML, like Gecko) PingdomTMS/0.8.5 Safari/534.34' - - regex: '(PingdomTMS)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'PingdomBot' - # 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/61.0.3163.100 Chrome/61.0.3163.100 Safari/537.36 PingdomPageSpeed/1.0 (pingbot/2.0; +http://www.pingdom.com/)' - - regex: '(PingdomPageSpeed)/(\d+)\.(\d+)' - family_replacement: 'PingdomBot' - - # PTST / WebPageTest.org crawlers + - regex: (CFNetwork)(?:/(\d+)\.(\d+)(?:\.(\d+)|)|) + family_replacement: CFNetwork + - regex: (Pingdom\.com_bot_version_)(\d+)\.(\d+) + family_replacement: PingdomBot + - regex: (PingdomTMS)/(\d+)\.(\d+)\.(\d+) + family_replacement: PingdomBot + - regex: (PingdomPageSpeed)/(\d+)\.(\d+) + family_replacement: PingdomBot - regex: ' (PTST)/(\d+)(?:\.(\d+)|)$' - family_replacement: 'WebPageTest.org bot' - - # Datanyze.com spider - - regex: 'X11; (Datanyze); Linux' - - # New Relic Pinger - - regex: '(NewRelicPinger)/(\d+)\.(\d+)' - family_replacement: 'NewRelicPingerBot' - - # Tableau - - regex: '(Tableau)/(\d+)\.(\d+)' - family_replacement: 'Tableau' - - # Adobe CreativeCloud - - regex: 'AppleWebKit/\d{1,10}\.\d{1,10}.{0,200} Safari.{0,200} (CreativeCloud)/(\d+)\.(\d+).(\d+)' - family_replacement: 'Adobe CreativeCloud' - - # Salesforce - - regex: '(Salesforce)(?:.)\/(\d+)\.(\d?)' - - #StatusCake - - regex: '(\(StatusCake\))' - family_replacement: 'StatusCakeBot' - - # Facebook - - regex: '(facebookexternalhit)/(\d+)\.(\d+)' - family_replacement: 'FacebookBot' - - # Google Plus - - regex: 'Google.{0,50}/\+/web/snippet' - family_replacement: 'GooglePlusBot' - - # Gmail - - regex: 'via ggpht\.com GoogleImageProxy' - family_replacement: 'GmailImageProxy' - - # Yahoo - - regex: 'YahooMailProxy; https://help\.yahoo\.com/kb/yahoo-mail-proxy-SLN28749\.html' - family_replacement: 'YahooMailProxy' - - # Twitter - - regex: '(Twitterbot)/(\d+)\.(\d+)' - family_replacement: 'Twitterbot' - - # Bots Pattern 'name/0.0.0' - - regex: '/((?:Ant-|)Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \-](\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - # Bots Pattern 'name/0.0.0' - - regex: '\b(008|Altresium|Argus|BaiduMobaider|BoardReader|DNSGroup|DataparkSearch|EDI|Goodzer|Grub|INGRID|Infohelfer|LinkedInBot|LOOQ|Nutch|OgScrper|Pandora|PathDefender|Peew|PostPost|Steeler|Twitterbot|VSE|WebCrunch|WebZIP|Y!J-BR[A-Z]|YahooSeeker|envolk|sproose|wminer)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - - # MSIECrawler - - regex: '(MSIE) (\d+)\.(\d+)([a-z]\d|[a-z]|);.{0,200} MSIECrawler' - family_replacement: 'MSIECrawler' - - # DAVdroid - - regex: '(DAVdroid)/(\d+)\.(\d+)(?:\.(\d+)|)' - - # Downloader ... - - regex: '(Google-HTTP-Java-Client|Apache-HttpClient|PostmanRuntime|Go-http-client|scalaj-http|http%20client|Python-urllib|HttpMonitor|TLSProber|WinHTTP|JNLP|okhttp|aihttp|reqwest|axios|unirest-(?:java|python|ruby|nodejs|php|net))(?:[ /](\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)' - - # Pinterestbot - - regex: '(Pinterest(?:bot|))/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)[;\s(]+\+https://www.pinterest.com/bot.html' - family_replacement: 'Pinterestbot' - - # Bots - - regex: '(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\/\$BotVersion|123metaspider-Bot|1470\.net crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\.org_bot|archiver|Ask Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\b\w{0,30}favicon\w{0,30}\b|\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo Screenshot Bot|PHPCrawl|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|Tiny Tiny RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s|) Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg|ArcGIS Hub Indexer)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+)|)|)|)' - - # AWS S3 Clients - # must come before "Bots General matcher" to catch "boto"/"boto3" before "bot" - - regex: '\b(Boto3?|JetS3t|aws-(?:cli|sdk-(?:cpp|go|java|nodejs|ruby2?|dotnet-(?:\d{1,2}|core)))|s3fs)/(\d+)\.(\d+)(?:\.(\d+)|)' - - # SAFE FME - - regex: '(FME)\/(\d+\.\d+)\.(\d+)\.(\d+)' - - # QGIS - - regex: '(QGIS)\/(\d)\.?0?(\d{1,2})\.?0?(\d{1,2})' - - # JOSM - - regex: '(JOSM)/(\d+)\.(\d+)' - - # Tygron Platform - - regex: '(Tygron Platform) \((\d+)\.(\d+)\.(\d+(?:\.\d+| RC \d+\.\d+))' - - # Facebook - # Must come before "Bots General matcher" to catch OrangeBotswana - # Facebook Messenger must go before Facebook - - regex: '\[(FBAN/MessengerForiOS|FB_IAB/MESSENGER);FBAV/(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+)|)|)|)' - - family_replacement: 'Facebook Messenger' - # Facebook - - regex: '\[FB.{0,300};(FBAV)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - family_replacement: 'Facebook' - # Sometimes Facebook does not specify a version (FBAV) - - regex: '\[FB.{0,300};' - family_replacement: 'Facebook' - - # Bots General matcher 'name/0.0' - - regex: '^.{0,200}?(?:\/[A-Za-z0-9\.]{0,50}|) {0,2}([A-Za-z0-9 \-_\!\[\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50}))[/ ](\d+)(?:\.(\d+)(?:\.(\d+)|)|)' - # Bots containing bot(but not CUBOT) - - regex: '^.{0,200}?((?:[A-Za-z][A-Za-z0-9 -]{0,50}|)[^C][^Uu][Bb]ot)\b(?:(?:[ /]| v)(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)' - # Bots containing spider|scrape|Crawl - - regex: '^.{0,200}?((?:[A-z0-9]{1,50}|[A-z\-]{1,50} ?|)(?: the |)(?:[Ss][Pp][Ii][Dd][Ee][Rr]|[Ss]crape|[Cc][Rr][Aa][Ww][Ll])[A-z0-9]{0,50})(?:(?:[ /]| v)(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)' - - # HbbTV standard defines what features the browser should understand. - # but it's like targeting "HTML5 browsers", effective browser support depends on the model - # See os_parsers if you want to target a specific TV - - regex: '(HbbTV)/(\d+)\.(\d+)\.(\d+) \(' - - # must go before Firefox to catch Chimera/SeaMonkey/Camino/Waterfox - - regex: '(Chimera|SeaMonkey|Camino|Waterfox)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*|)' - - # must be before Firefox / Gecko to catch SailfishBrowser properly - - regex: '(SailfishBrowser)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Sailfish Browser' - - # Social Networks (non-Facebook) - # Pinterest - - regex: '\[(Pinterest)/[^\]]{1,50}\]' + family_replacement: WebPageTest.org bot + - regex: X11; (Datanyze); Linux + - regex: (NewRelicPinger)/(\d+)\.(\d+) + family_replacement: NewRelicPingerBot + - regex: (Tableau)/(\d+)\.(\d+) + family_replacement: Tableau + - regex: AppleWebKit/\d{1,10}\.\d{1,10}.{0,200} Safari.{0,200} (CreativeCloud)/(\d+)\.(\d+).(\d+) + family_replacement: Adobe CreativeCloud + - regex: (Salesforce)(?:.)\/(\d+)\.(\d?) + - regex: (\(StatusCake\)) + family_replacement: StatusCakeBot + - regex: (facebookexternalhit)/(\d+)\.(\d+) + family_replacement: FacebookBot + - regex: Google.{0,50}/\+/web/snippet + family_replacement: GooglePlusBot + - regex: via ggpht\.com GoogleImageProxy + family_replacement: GmailImageProxy + - regex: YahooMailProxy; https://help\.yahoo\.com/kb/yahoo-mail-proxy-SLN28749\.html + family_replacement: YahooMailProxy + - regex: (Twitterbot)/(\d+)\.(\d+) + family_replacement: Twitterbot + - regex: /((?:Ant-|)Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ + \-](\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: \b(008|Altresium|Argus|BaiduMobaider|BoardReader|DNSGroup|DataparkSearch|EDI|Goodzer|Grub|INGRID|Infohelfer|LinkedInBot|LOOQ|Nutch|OgScrper|Pandora|PathDefender|Peew|PostPost|Steeler|Twitterbot|VSE|WebCrunch|WebZIP|Y!J-BR[A-Z]|YahooSeeker|envolk|sproose|wminer)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (MSIE) (\d+)\.(\d+)([a-z]\d|[a-z]|);.{0,200} MSIECrawler + family_replacement: MSIECrawler + - regex: (DAVdroid)/(\d+)\.(\d+)(?:\.(\d+)|) + - regex: (Google-HTTP-Java-Client|Apache-HttpClient|PostmanRuntime|Go-http-client|scalaj-http|http%20client|Python-urllib|HttpMonitor|TLSProber|WinHTTP|JNLP|okhttp|aihttp|reqwest|axios|unirest-(?:java|python|ruby|nodejs|php|net))(?:[ + /](\d+)(?:\.(\d+)|)(?:\.(\d+)|)|) + - regex: (Pinterest(?:bot|))/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)[;\s(]+\+https://www.pinterest.com/bot.html + family_replacement: Pinterestbot + - regex: '(CSimpleSpider|Cityreview Robot|CrawlDaddy|CrawlFire|Finderbots|Index + crawler|Job Roboter|KiwiStatus Spider|Lijit Crawler|QuerySeekerSpider|ScollSpider|Trends + Crawler|USyd-NLP-Spider|SiteCat Webbot|BotName\/\$BotVersion|123metaspider-Bot|1470\.net + crawler|50\.nu|8bo Crawler Bot|Aboundex|Accoona-[A-z]{1,30}-Agent|AdsBot-Google(?:-[a-z]{1,30}|)|altavista|AppEngine-Google|archive.{0,30}\.org_bot|archiver|Ask + Jeeves|[Bb]ai[Dd]u[Ss]pider(?:-[A-Za-z]{1,30})(?:-[A-Za-z]{1,30}|)|bingbot|BingPreview|blitzbot|BlogBridge|Bloglovin|BoardReader + Blog Indexer|BoardReader Favicon Fetcher|boitho.com-dc|BotSeer|BUbiNG|\b\w{0,30}favicon\w{0,30}\b|\bYeti(?:-[a-z]{1,30}|)|Catchpoint(?: + bot|)|[Cc]harlotte|Checklinks|clumboot|Comodo HTTP\(S\) Crawler|Comodo-Webinspector-Crawler|ConveraCrawler|CRAWL-E|CrawlConvera|Daumoa(?:-feedfetcher|)|Feed + Seeker Bot|Feedbin|findlinks|Flamingo_SearchEngine|FollowSite Bot|furlbot|Genieo|gigabot|GomezAgent|gonzo1|(?:[a-zA-Z]{1,30}-|)Googlebot(?:-[a-zA-Z]{1,30}|)|Google + SketchUp|grub-client|gsa-crawler|heritrix|HiddenMarket|holmes|HooWWWer|htdig|ia_archiver|ICC-Crawler|Icarus6j|ichiro(?:/mobile|)|IconSurf|IlTrovatore(?:-Setaccio|)|InfuzApp|Innovazion + Crawler|InternetArchive|IP2[a-z]{1,30}Bot|jbot\b|KaloogaBot|Kraken|Kurzor|larbin|LEIA|LesnikBot|Linguee + Bot|LinkAider|LinkedInBot|Lite Bot|Llaut|lycos|Mail\.RU_Bot|masscan|masidani_bot|Mediapartners-Google|Microsoft + .{0,30} Bot|mogimogi|mozDex|MJ12bot|msnbot(?:-media {0,2}|)|msrbot|Mtps Feed + Aggregation System|netresearch|Netvibes|NewsGator[^/]{0,30}|^NING|Nutch[^/]{0,30}|Nymesis|ObjectsSearch|OgScrper|Orbiter|OOZBOT|PagePeeker|PagesInventory|PaxleFramework|Peeplo + Screenshot Bot|PHPCrawl|PlantyNet_WebRobot|Pompos|Qwantify|Read%20Later|Reaper|RedCarpet|Retreiver|Riddler|Rival + IQ|scooter|Scrapy|Scrubby|searchsight|seekbot|semanticdiscovery|SemrushBot|Simpy|SimplePie|SEOstats|SimpleRSS|SiteCon|Slackbot-LinkExpanding|Slack-ImgProxy|Slurp|snappy|Speedy + Spider|Squrl Java|Stringer|TheUsefulbot|ThumbShotsBot|Thumbshots\.ru|Tiny Tiny + RSS|Twitterbot|WhatsApp|URL2PNG|Vagabondo|VoilaBot|^vortex|Votay bot|^voyager|WASALive.Bot|Web-sniffer|WebThumb|WeSEE:[A-z]{1,30}|WhatWeb|WIRE|WordPress|Wotbox|www\.almaden\.ibm\.com|Xenu(?:.s|) + Link Sleuth|Xerka [A-z]{1,30}Bot|yacy(?:bot|)|YahooSeeker|Yahoo! Slurp|Yandex\w{1,30}|YodaoBot(?:-[A-z]{1,30}|)|YottaaMonitor|Yowedo|^Zao|^Zao-Crawler|ZeBot_www\.ze\.bz|ZooShot|ZyBorg|ArcGIS + Hub Indexer)(?:[ /]v?(\d+)(?:\.(\d+)(?:\.(\d+)|)|)|)' + - regex: \b(Boto3?|JetS3t|aws-(?:cli|sdk-(?:cpp|go|java|nodejs|ruby2?|dotnet-(?:\d{1,2}|core)))|s3fs)/(\d+)\.(\d+)(?:\.(\d+)|) + - regex: (FME)\/(\d+\.\d+)\.(\d+)\.(\d+) + - regex: (QGIS)\/(\d)\.?0?(\d{1,2})\.?0?(\d{1,2}) + - regex: (JOSM)/(\d+)\.(\d+) + - regex: (Tygron Platform) \((\d+)\.(\d+)\.(\d+(?:\.\d+| RC \d+\.\d+)) + - regex: \[(FBAN/MessengerForiOS|FB_IAB/MESSENGER);FBAV/(\d+)(?:\.(\d+)(?:\.(\d+)(?:\.(\d+)|)|)|) + family_replacement: Facebook Messenger + - regex: \[FB.{0,300};(FBAV)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + family_replacement: Facebook + - regex: \[FB.{0,300}; + family_replacement: Facebook + - regex: ^.{0,200}?(?:\/[A-Za-z0-9\.]{0,50}|) {0,2}([A-Za-z0-9 \-_\!\[\]:]{0,50}(?:[Aa]rchiver|[Ii]ndexer|[Ss]craper|[Bb]ot|[Ss]pider|[Cc]rawl[a-z]{0,50}))[/ + ](\d+)(?:\.(\d+)(?:\.(\d+)|)|) + - regex: ^.{0,200}?((?:[A-Za-z][A-Za-z0-9 -]{0,50}|)[^C][^Uu][Bb]ot)\b(?:(?:[ /]| + v)(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|) + - regex: '^.{0,200}?((?:[A-z0-9]{1,50}|[A-z\-]{1,50} ?|)(?: the |)(?:[Ss][Pp][Ii][Dd][Ee][Rr]|[Ss]crape|[Cc][Rr][Aa][Ww][Ll])[A-z0-9]{0,50})(?:(?:[ + /]| v)(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)' + - regex: (HbbTV)/(\d+)\.(\d+)\.(\d+) \( + - regex: (Chimera|SeaMonkey|Camino|Waterfox)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*|) + - regex: (SailfishBrowser)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Sailfish Browser + - regex: \[(Pinterest)/[^\]]{1,50}\] - regex: '(Pinterest)(?: for Android(?: Tablet|)|)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - # Instagram app - - regex: 'Mozilla.{1,200}Mobile.{1,100}(Instagram).(\d+)\.(\d+)\.(\d+)' - # Flipboard app - - regex: 'Mozilla.{1,200}Mobile.{1,100}(Flipboard).(\d+)\.(\d+)\.(\d+)' - # Flipboard-briefing app - - regex: 'Mozilla.{1,200}Mobile.{1,100}(Flipboard-Briefing).(\d+)\.(\d+)\.(\d+)' - # Onefootball app - - regex: 'Mozilla.{1,200}Mobile.{1,100}(Onefootball)\/Android.(\d+)\.(\d+)\.(\d+)' - # Snapchat - - regex: '(Snapchat)\/(\d+)\.(\d+)\.(\d+)\.(\d+)' - # Twitter - - regex: '(Twitter for (?:iPhone|iPad)|TwitterAndroid)(?:\/(\d+)\.(\d+)|)' - family_replacement: 'Twitter' - - # Phantom app - - regex: 'Mozilla.{1,200}Mobile.{1,100}(Phantom\/ios|android).(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Phantom' - - # aspiegel.com spider (owned by Huawei, later called PetalBot) - - regex: 'Mozilla.{1,100}Mobile.{1,100}(AspiegelBot|PetalBot)' - family_replacement: 'Spider' - - - regex: 'AspiegelBot|PetalBot' - family_replacement: 'Spider' - - # Basilisk - - regex: '(Firefox)/(\d+)\.(\d+) Basilisk/(\d+)' - family_replacement: 'Basilisk' - - # Pale Moon - - regex: '(PaleMoon)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Pale Moon' - - # Firefox - - regex: '(Fennec)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*)' - family_replacement: 'Firefox Mobile' - - regex: '(Fennec)/(\d+)\.(\d+)(pre)' - family_replacement: 'Firefox Mobile' - - regex: '(Fennec)/(\d+)\.(\d+)' - family_replacement: 'Firefox Mobile' - - regex: '(?:Mobile|Tablet);.{0,200}(Firefox)/(\d+)\.(\d+)' - family_replacement: 'Firefox Mobile' - - regex: '(Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)\.(\d+(?:pre|))' - family_replacement: 'Firefox ($1)' - - regex: '(Firefox)/(\d+)\.(\d+)(a\d+[a-z]*)' - family_replacement: 'Firefox Alpha' - - regex: '(Firefox)/(\d+)\.(\d+)(b\d+[a-z]*)' - family_replacement: 'Firefox Beta' - - regex: '(Firefox)-(?:\d+\.\d+|)/(\d+)\.(\d+)(a\d+[a-z]*)' - family_replacement: 'Firefox Alpha' - - regex: '(Firefox)-(?:\d+\.\d+|)/(\d+)\.(\d+)(b\d+[a-z]*)' - family_replacement: 'Firefox Beta' - - regex: '(Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)([ab]\d+[a-z]*|)' - family_replacement: 'Firefox ($1)' - - regex: '(Firefox).{0,200}Tablet browser (\d+)\.(\d+)\.(\d+)' - family_replacement: 'MicroB' - - regex: '(MozillaDeveloperPreview)/(\d+)\.(\d+)([ab]\d+[a-z]*|)' - - regex: '(FxiOS)/(\d+)\.(\d+)(\.(\d+)|)(\.(\d+)|)' - family_replacement: 'Firefox iOS' - - # e.g.: Flock/2.0b2 - - regex: '(Flock)/(\d+)\.(\d+)(b\d+?)' - - # RockMelt - - regex: '(RockMelt)/(\d+)\.(\d+)\.(\d+)' - - # e.g.: Fennec/0.9pre - - regex: '(Navigator)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Netscape' - - - regex: '(Navigator)/(\d+)\.(\d+)([ab]\d+)' - family_replacement: 'Netscape' - - - regex: '(Netscape6)/(\d+)\.(\d+)\.?([ab]?\d+|)' - family_replacement: 'Netscape' - - - regex: '(MyIBrow)/(\d+)\.(\d+)' - family_replacement: 'My Internet Browser' - - # UC Browser - # we need check it before opera. In other case case UC Browser detected look like Opera Mini - - regex: '(UC? ?Browser|UCWEB|U3)[ /]?(\d+)\.(\d+)\.(\d+)' - family_replacement: 'UC Browser' - - # Opera will stop at 9.80 and hide the real version in the Version string. - # see: http://dev.opera.com/articles/view/opera-ua-string-changes/ - - regex: '(Opera Tablet).{0,200}Version/(\d+)\.(\d+)(?:\.(\d+)|)' - - regex: '(Opera Mini)(?:/att|)/?(\d+|)(?:\.(\d+)|)(?:\.(\d+)|)' - - regex: '(Opera)/.{1,100}Opera Mobi.{1,100}Version/(\d+)\.(\d+)' - family_replacement: 'Opera Mobile' - - regex: '(Opera)/(\d+)\.(\d+).{1,100}Opera Mobi' - family_replacement: 'Opera Mobile' - - regex: 'Opera Mobi.{1,100}(Opera)(?:/|\s+)(\d+)\.(\d+)' - family_replacement: 'Opera Mobile' - - regex: 'Opera Mobi' - family_replacement: 'Opera Mobile' - - regex: '(Opera)/9.80.{0,200}Version/(\d+)\.(\d+)(?:\.(\d+)|)' - - # Opera 14 for Android uses a WebKit render engine. - - regex: '(?:Mobile Safari).{1,300}(OPR)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Opera Mobile' - - # Opera >=15 for Desktop is similar to Chrome but includes an "OPR" Version string. - - regex: '(?:Chrome).{1,300}(OPR)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Opera' - - # Opera Coast - - regex: '(Coast)/(\d+).(\d+).(\d+)' - family_replacement: 'Opera Coast' - - # Opera Mini for iOS (from version 8.0.0) - - regex: '(OPiOS)/(\d+).(\d+).(\d+)' - family_replacement: 'Opera Mini' - - # Opera Neon - - regex: 'Chrome/.{1,200}( MMS)/(\d+).(\d+).(\d+)' - family_replacement: 'Opera Neon' - - # Palm WebOS looks a lot like Safari. - - regex: '(hpw|web)OS/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'webOS Browser' - - # LuaKit has no version info. - # http://luakit.org/projects/luakit/ - - regex: '(luakit)' - family_replacement: 'LuaKit' - - # Snowshoe - - regex: '(Snowshoe)/(\d+)\.(\d+).(\d+)' - - # Lightning (for Thunderbird) - # http://www.mozilla.org/projects/calendar/lightning/ - - regex: 'Gecko/\d+ (Lightning)/(\d+)\.(\d+)\.?((?:[ab]?\d+[a-z]*)|(?:\d*))' - - # Swiftfox - - regex: '(Firefox)/(\d+)\.(\d+)\.(\d+(?:pre|)) \(Swiftfox\)' - family_replacement: 'Swiftfox' - - regex: '(Firefox)/(\d+)\.(\d+)([ab]\d+[a-z]*|) \(Swiftfox\)' - family_replacement: 'Swiftfox' - - # Rekonq - - regex: '(rekonq)/(\d+)\.(\d+)(?:\.(\d+)|) Safari' - family_replacement: 'Rekonq' - - regex: 'rekonq' - family_replacement: 'Rekonq' - - # Conkeror lowercase/uppercase - # http://conkeror.org/ - - regex: '(conkeror|Conkeror)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Conkeror' - - # catches lower case konqueror - - regex: '(konqueror)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Konqueror' - - - regex: '(WeTab)-Browser' - - - regex: '(Comodo_Dragon)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Comodo Dragon' - - - regex: '(Symphony) (\d+).(\d+)' - - - regex: 'PLAYSTATION 3.{1,200}WebKit' - family_replacement: 'NetFront NX' - - regex: 'PLAYSTATION 3' - family_replacement: 'NetFront' - - regex: '(PlayStation Portable)' - family_replacement: 'NetFront' - - regex: '(PlayStation Vita)' - family_replacement: 'NetFront NX' - - - regex: 'AppleWebKit.{1,200} (NX)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'NetFront NX' - - regex: '(Nintendo 3DS)' - family_replacement: 'NetFront NX' - - # Amazon Silk, should go before Safari and Chrome Mobile - - regex: '(Silk)/(\d+)\.(\d+)(?:\.([0-9\-]+)|)' - family_replacement: 'Amazon Silk' - - # @ref: http://www.puffinbrowser.com - - regex: '(Puffin)/(\d+)\.(\d+)(?:\.(\d+)|)' - - # Edge Mobile - - regex: 'Windows Phone .{0,200}(Edge)/(\d+)\.(\d+)' - family_replacement: 'Edge Mobile' - - regex: '(EdgiOS|EdgA)/(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Edge Mobile' - - # Oculus Browser, should go before Samsung Internet - - regex: '(OculusBrowser)/(\d+)\.(\d+).0.0(?:\.([0-9\-]+)|)' - family_replacement: 'Oculus Browser' - - # Samsung Internet (based on Chrome, but lacking some features) - - regex: '(SamsungBrowser)/(\d+)\.(\d+)' - family_replacement: 'Samsung Internet' - - # Seznam.cz browser (based on WebKit) - - regex: '(SznProhlizec)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Seznam prohlížeč' - - # Coc Coc browser, based on Chrome (used in Vietnam) - - regex: '(coc_coc_browser)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Coc Coc' - - # Baidu Browsers (desktop spoofs chrome & IE, explorer is mobile) - - regex: '(baidubrowser)[/\s](\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - family_replacement: 'Baidu Browser' - - regex: '(FlyFlow)/(\d+)\.(\d+)' - family_replacement: 'Baidu Explorer' - - # MxBrowser is Maxthon. Must go before Mobile Chrome for Android - - regex: '(MxBrowser)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Maxthon' - - # Crosswalk must go before Mobile Chrome for Android - - regex: '(Crosswalk)/(\d+)\.(\d+)\.(\d+)\.(\d+)' - - # LINE https://line.me/en/ - # Must go before Mobile Chrome for Android - - regex: '(Line)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'LINE' - - # MiuiBrowser should got before Mobile Chrome for Android - - regex: '(MiuiBrowser)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'MiuiBrowser' - - # Mint Browser should got before Mobile Chrome for Android - - regex: '(Mint Browser)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Mint Browser' - - # TopBuzz Android must go before Chrome Mobile WebView - - regex: '(TopBuzz)/(\d+).(\d+).(\d+)' - family_replacement: 'TopBuzz' - - # Google Search App on Android, eg: - - regex: 'Mozilla.{1,200}Android.{1,200}(GSA)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Google' - - # QQ Browsers - - regex: '(MQQBrowser/Mini)(?:(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)' - family_replacement: 'QQ Browser Mini' - - regex: '(MQQBrowser)(?:/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)' - family_replacement: 'QQ Browser Mobile' - - regex: '(QQBrowser)(?:/(\d+)(?:\.(\d+)\.(\d+)(?:\.(\d+)|)|)|)' - family_replacement: 'QQ Browser' - - # DuckDuckGo - - regex: 'Mobile.{0,200}(DuckDuckGo)/(\d+)' - family_replacement: 'DuckDuckGo Mobile' - - # Tenta Browser - - regex: '(Tenta/)(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Tenta Browser' - - # Chrome Mobile - - regex: 'Version/.{1,300}(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Chrome Mobile WebView' - - regex: '; wv\).{1,300}(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Chrome Mobile WebView' - - regex: '(CrMo)/(\d+)\.(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Chrome Mobile' - - regex: '(CriOS)/(\d+)\.(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Chrome Mobile iOS' - - regex: '(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+) Mobile(?:[ /]|$)' - family_replacement: 'Chrome Mobile' + - regex: Mozilla.{1,200}Mobile.{1,100}(Instagram).(\d+)\.(\d+)\.(\d+) + - regex: Mozilla.{1,200}Mobile.{1,100}(Flipboard).(\d+)\.(\d+)\.(\d+) + - regex: Mozilla.{1,200}Mobile.{1,100}(Flipboard-Briefing).(\d+)\.(\d+)\.(\d+) + - regex: Mozilla.{1,200}Mobile.{1,100}(Onefootball)\/Android.(\d+)\.(\d+)\.(\d+) + - regex: (Snapchat)\/(\d+)\.(\d+)\.(\d+)\.(\d+) + - regex: (Twitter for (?:iPhone|iPad)|TwitterAndroid)(?:\/(\d+)\.(\d+)|) + family_replacement: Twitter + - regex: Mozilla.{1,200}Mobile.{1,100}(Phantom\/ios|Phantom\/android).(\d+)\.(\d+)\.(\d+) + family_replacement: Phantom + - regex: Mozilla.{1,100}Mobile.{1,100}(AspiegelBot|PetalBot) + family_replacement: Spider + - regex: AspiegelBot|PetalBot + family_replacement: Spider + - regex: (Firefox)/(\d+)\.(\d+) Basilisk/(\d+) + family_replacement: Basilisk + - regex: (PaleMoon)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Pale Moon + - regex: (Fennec)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*) + family_replacement: Firefox Mobile + - regex: (Fennec)/(\d+)\.(\d+)(pre) + family_replacement: Firefox Mobile + - regex: (Fennec)/(\d+)\.(\d+) + family_replacement: Firefox Mobile + - regex: (?:Mobile|Tablet);.{0,200}(Firefox)/(\d+)\.(\d+) + family_replacement: Firefox Mobile + - regex: (Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)\.(\d+(?:pre|)) + family_replacement: Firefox ($1) + - regex: (Firefox)/(\d+)\.(\d+)(a\d+[a-z]*) + family_replacement: Firefox Alpha + - regex: (Firefox)/(\d+)\.(\d+)(b\d+[a-z]*) + family_replacement: Firefox Beta + - regex: (Firefox)-(?:\d+\.\d+|)/(\d+)\.(\d+)(a\d+[a-z]*) + family_replacement: Firefox Alpha + - regex: (Firefox)-(?:\d+\.\d+|)/(\d+)\.(\d+)(b\d+[a-z]*) + family_replacement: Firefox Beta + - regex: (Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)([ab]\d+[a-z]*|) + family_replacement: Firefox ($1) + - regex: (Firefox).{0,200}Tablet browser (\d+)\.(\d+)\.(\d+) + family_replacement: MicroB + - regex: (MozillaDeveloperPreview)/(\d+)\.(\d+)([ab]\d+[a-z]*|) + - regex: (FxiOS)/(\d+)\.(\d+)(\.(\d+)|)(\.(\d+)|) + family_replacement: Firefox iOS + - regex: (Flock)/(\d+)\.(\d+)(b\d+?) + - regex: (RockMelt)/(\d+)\.(\d+)\.(\d+) + - regex: (Navigator)/(\d+)\.(\d+)\.(\d+) + family_replacement: Netscape + - regex: (Navigator)/(\d+)\.(\d+)([ab]\d+) + family_replacement: Netscape + - regex: (Netscape6)/(\d+)\.(\d+)\.?([ab]?\d+|) + family_replacement: Netscape + - regex: (MyIBrow)/(\d+)\.(\d+) + family_replacement: My Internet Browser + - regex: (UC? ?Browser|UCWEB|U3)[ /]?(\d+)\.(\d+)\.(\d+) + family_replacement: UC Browser + - regex: (Opera Tablet).{0,200}Version/(\d+)\.(\d+)(?:\.(\d+)|) + - regex: (Opera Mini)(?:/att|)/?(\d+|)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (Opera)/.{1,100}Opera Mobi.{1,100}Version/(\d+)\.(\d+) + family_replacement: Opera Mobile + - regex: (Opera)/(\d+)\.(\d+).{1,100}Opera Mobi + family_replacement: Opera Mobile + - regex: Opera Mobi.{1,100}(Opera)(?:/|\s+)(\d+)\.(\d+) + family_replacement: Opera Mobile + - regex: Opera Mobi + family_replacement: Opera Mobile + - regex: (Opera)/9.80.{0,200}Version/(\d+)\.(\d+)(?:\.(\d+)|) + - regex: (?:Mobile Safari).{1,300}(OPR)/(\d+)\.(\d+)\.(\d+) + family_replacement: Opera Mobile + - regex: (?:Chrome).{1,300}(OPR)/(\d+)\.(\d+)\.(\d+) + family_replacement: Opera + - regex: (Coast)/(\d+).(\d+).(\d+) + family_replacement: Opera Coast + - regex: (OPiOS)/(\d+).(\d+).(\d+) + family_replacement: Opera Mini + - regex: Chrome/.{1,200}( MMS)/(\d+).(\d+).(\d+) + family_replacement: Opera Neon + - regex: (hpw|web)OS/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: webOS Browser + - regex: (luakit) + family_replacement: LuaKit + - regex: (Snowshoe)/(\d+)\.(\d+).(\d+) + - regex: Gecko/\d+ (Lightning)/(\d+)\.(\d+)\.?((?:[ab]?\d+[a-z]*)|(?:\d*)) + - regex: (Firefox)/(\d+)\.(\d+)\.(\d+(?:pre|)) \(Swiftfox\) + family_replacement: Swiftfox + - regex: (Firefox)/(\d+)\.(\d+)([ab]\d+[a-z]*|) \(Swiftfox\) + family_replacement: Swiftfox + - regex: (rekonq)/(\d+)\.(\d+)(?:\.(\d+)|) Safari + family_replacement: Rekonq + - regex: rekonq + family_replacement: Rekonq + - regex: (conkeror|Conkeror)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Conkeror + - regex: (konqueror)/(\d+)\.(\d+)\.(\d+) + family_replacement: Konqueror + - regex: (WeTab)-Browser + - regex: (Comodo_Dragon)/(\d+)\.(\d+)\.(\d+) + family_replacement: Comodo Dragon + - regex: (Symphony) (\d+).(\d+) + - regex: PLAYSTATION 3.{1,200}WebKit + family_replacement: NetFront NX + - regex: PLAYSTATION 3 + family_replacement: NetFront + - regex: (PlayStation Portable) + family_replacement: NetFront + - regex: (PlayStation Vita) + family_replacement: NetFront NX + - regex: AppleWebKit.{1,200} (NX)/(\d+)\.(\d+)\.(\d+) + family_replacement: NetFront NX + - regex: (Nintendo 3DS) + family_replacement: NetFront NX + - regex: (HuaweiBrowser)/(\d+)\.(\d+)\.(\d+)\.\d+ + family_replacement: Huawei Browser + - regex: (AVG)/(\d+)\.(\d+)\.(\d+)\.\d+ + family_replacement: AVG + - regex: (AvastSecureBrowser|Avast)/(\d+)\.(\d+)\.(\d+) + family_replacement: Avast Secure Browser + - regex: (Instabridge)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (AlohaBrowser)/(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Aloha Browser + - regex: ((?:B|b)rave(?:\sChrome)?)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) + family_replacement: Brave + - regex: (Silk)/(\d+)\.(\d+)(?:\.([0-9\-]+)|) + family_replacement: Amazon Silk + - regex: (Puffin)/(\d+)\.(\d+)(?:\.(\d+)|) + - regex: Windows Phone .{0,200}(Edge)/(\d+)\.(\d+) + family_replacement: Edge Mobile + - regex: (EdgiOS|EdgA)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) + family_replacement: Edge Mobile + - regex: (OculusBrowser)/(\d+)\.(\d+).0.0(?:\.([0-9\-]+)|) + family_replacement: Oculus Browser + - regex: (SamsungBrowser)/(\d+)\.(\d+) + family_replacement: Samsung Internet + - regex: (SznProhlizec)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Seznam prohlížeč + - regex: (coc_coc_browser)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Coc Coc + - regex: (baidubrowser)[/\s](\d+)(?:\.(\d+)|)(?:\.(\d+)|) + family_replacement: Baidu Browser + - regex: (FlyFlow)/(\d+)\.(\d+) + family_replacement: Baidu Explorer + - regex: (MxBrowser)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Maxthon + - regex: (Crosswalk)/(\d+)\.(\d+)\.(\d+)\.(\d+) + - regex: (Line)/(\d+)\.(\d+)\.(\d+) + family_replacement: LINE + - regex: (MiuiBrowser)/(\d+)\.(\d+)\.(\d+) + family_replacement: MiuiBrowser + - regex: (Mint Browser)/(\d+)\.(\d+)\.(\d+) + family_replacement: Mint Browser + - regex: (TopBuzz)/(\d+).(\d+).(\d+) + family_replacement: TopBuzz + - regex: Mozilla.{1,200}Android.{1,200}(GSA)/(\d+)\.(\d+)\.(\d+) + family_replacement: Google + - regex: (MQQBrowser/Mini)(?:(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|) + family_replacement: QQ Browser Mini + - regex: (MQQBrowser)(?:/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)|) + family_replacement: QQ Browser Mobile + - regex: (QQBrowser)(?:/(\d+)(?:\.(\d+)\.(\d+)(?:\.(\d+)|)|)|) + family_replacement: QQ Browser + - regex: Mozilla.{1,200}Mobile.{1,100}(DuckDuckGo)/(\d+) + family_replacement: DuckDuckGo Mobile + - regex: Mozilla.{1,200}(DuckDuckGo)/(\d+) + family_replacement: DuckDuckGo + - regex: Mozilla.{1,200}Mobile.{1,100}(Ddg)/(\d+)(?:\.(\d+)|) + family_replacement: DuckDuckGo Mobile + - regex: Mozilla.{1,200}(Ddg)/(\d+)(?:\.(\d+)|) + family_replacement: DuckDuckGo + - regex: (Tenta/)(\d+)\.(\d+)\.(\d+) + family_replacement: Tenta Browser + - regex: (Ecosia) ios@(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) + family_replacement: Ecosia iOS + - regex: (Ecosia) android@(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) + family_replacement: Ecosia Android + - regex: Version/.{1,300}(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+) + family_replacement: Chrome Mobile WebView + - regex: ; wv\).{1,300}(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+) + family_replacement: Chrome Mobile WebView + - regex: (CrMo)/(\d+)\.(\d+)\.(\d+)\.(\d+) + family_replacement: Chrome Mobile + - regex: (CriOS)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) + family_replacement: Chrome Mobile iOS + - regex: (Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+) Mobile(?:[ /]|$) + family_replacement: Chrome Mobile - regex: ' Mobile .{1,300}(Chrome)/(\d+)\.(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Chrome Mobile' - - # Chrome Frame must come before MSIE. - - regex: '(chromeframe)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Chrome Frame' - - # Tizen Browser (second case included in browser/major.minor regex) - - regex: '(SLP Browser)/(\d+)\.(\d+)' - family_replacement: 'Tizen Browser' - - # Sogou Explorer 2.X - - regex: '(SE 2\.X) MetaSr (\d+)\.(\d+)' - family_replacement: 'Sogou Explorer' - - # Rackspace Monitoring - - regex: '(Rackspace Monitoring)/(\d+)\.(\d+)' - family_replacement: 'RackspaceBot' - - # PRTG Network Monitoring - - regex: '(PRTG Network Monitor)' - - # PyAMF - - regex: '(PyAMF)/(\d+)\.(\d+)\.(\d+)' - - # Yandex Browser - - regex: '(YaBrowser)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Yandex Browser' - - # Mail.ru Amigo/Internet Browser (Chromium-based) - - regex: '(Chrome)/(\d+)\.(\d+)\.(\d+).{0,100} MRCHROME' - family_replacement: 'Mail.ru Chromium Browser' - - # AOL Browser (IE-based) - - regex: '(AOL) (\d+)\.(\d+); AOLBuild (\d+)' - - # Podcast catcher Applications using iTunes - - regex: '(PodCruncher|Downcast)[ /]?(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|)' - - # Box Notes https://www.box.com/resources/downloads - # Must be before Electron + family_replacement: Chrome Mobile + - regex: (chromeframe)/(\d+)\.(\d+)\.(\d+) + family_replacement: Chrome Frame + - regex: (SLP Browser)/(\d+)\.(\d+) + family_replacement: Tizen Browser + - regex: (SE 2\.X) MetaSr (\d+)\.(\d+) + family_replacement: Sogou Explorer + - regex: (Rackspace Monitoring)/(\d+)\.(\d+) + family_replacement: RackspaceBot + - regex: (PRTG Network Monitor) + - regex: (PyAMF)/(\d+)\.(\d+)\.(\d+) + - regex: (YaBrowser)/(\d+)\.(\d+)\.(\d+) + family_replacement: Yandex Browser + - regex: (YaSearchBrowser)/(\d+)\.(\d+)\.(\d+) + family_replacement: Yandex Browser + - regex: (Chrome)/(\d+)\.(\d+)\.(\d+).{0,100} MRCHROME + family_replacement: Mail.ru Chromium Browser + - regex: (AOL) (\d+)\.(\d+); AOLBuild (\d+) + - regex: (PodCruncher|Downcast)[ /]?(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) - regex: ' (BoxNotes)/(\d+)\.(\d+)\.(\d+)' - - # Whale - - regex: '(Whale)/(\d+)\.(\d+)\.(\d+)\.(\d+) Mobile(?:[ /]|$)' - family_replacement: 'Whale' - - - regex: '(Whale)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Whale' - - # 1Password - - regex: '(1Password)/(\d+)\.(\d+)\.(\d+)' - - # Ghost - # @ref: http://www.ghost.org - - regex: '(Ghost)/(\d+)\.(\d+)\.(\d+)' - - # Palo Alto GlobalProtect Linux - - regex: 'PAN (GlobalProtect)/(\d+)\.(\d+)\.(\d+) .{1,100} \(X11; Linux x86_64\)' - - # Surveyon https://www.surveyon.com/ - - regex: '^(surveyon)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Surveyon' - - #### END SPECIAL CASES TOP #### - - #### MAIN CASES - this catches > 50% of all browsers #### - - - # Slack desktop client (needs to be before Apple Mail, Electron, and Chrome as it gets wrongly detected on Mac OS otherwise) - - regex: '(Slack_SSB)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Slack Desktop Client' - - # HipChat provides a version on Mac, but not on Windows. - # Needs to be before Chrome on Windows, and AppleMail on Mac. - - regex: '(HipChat)/?(\d+|)' - family_replacement: 'HipChat Desktop Client' - - # Browser/major_version.minor_version.beta_version - - regex: '\b(MobileIron|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave|MacOutlook|Electron|OktaMobile)/(\d+)\.(\d+)\.(\d+)' - - # Outlook 2007 - - regex: 'Microsoft Office Outlook 12\.\d+\.\d+|MSOffice 12' - family_replacement: 'Outlook' + - regex: (Whale)/(\d+)\.(\d+)\.(\d+)\.(\d+) Mobile(?:[ /]|$) + family_replacement: Whale + - regex: (Whale)/(\d+)\.(\d+)\.(\d+) + family_replacement: Whale + - regex: (1Password)/(\d+)\.(\d+)\.(\d+) + - regex: (Ghost)/(\d+)\.(\d+)\.(\d+) + - regex: PAN (GlobalProtect)/(\d+)\.(\d+)\.(\d+) .{1,100} \(X11; Linux x86_64\) + - regex: ^(surveyon)/(\d+)\.(\d+)\.(\d+) + family_replacement: Surveyon + - regex: (Slack_SSB)/(\d+)\.(\d+)\.(\d+) + family_replacement: Slack Desktop Client + - regex: (HipChat)/?(\d+|) + family_replacement: HipChat Desktop Client + - regex: \b(MobileIron|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera + Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\.Browser|Bunjalloo|Google Earth|Raven + for Mac|Openwave|MacOutlook|Electron|OktaMobile)/(\d+)\.(\d+)\.(\d+) + - regex: Microsoft Office Outlook 12\.\d+\.\d+|MSOffice 12 + family_replacement: Outlook v1_replacement: '2007' - - # Outlook 2010 - - regex: 'Microsoft Outlook 14\.\d+\.\d+|MSOffice 14' - family_replacement: 'Outlook' + - regex: Microsoft Outlook 14\.\d+\.\d+|MSOffice 14 + family_replacement: Outlook v1_replacement: '2010' - - # Outlook 2013 - - regex: 'Microsoft Outlook 15\.\d+\.\d+' - family_replacement: 'Outlook' + - regex: Microsoft Outlook 15\.\d+\.\d+ + family_replacement: Outlook v1_replacement: '2013' - - # Outlook 2016 - - regex: 'Microsoft Outlook (?:Mail )?16\.\d+\.\d+|MSOffice 16' - family_replacement: 'Outlook' + - regex: Microsoft Outlook (?:Mail )?16\.\d+\.\d+|MSOffice 16 + family_replacement: Outlook v1_replacement: '2016' - - # Word 2014 - - regex: 'Microsoft Office (Word) 2014' - - # Windows Live Mail - - regex: 'Outlook-Express\/7\.0' - family_replacement: 'Windows Live Mail' - - # Apple Air Mail - - regex: '(Airmail) (\d+)\.(\d+)(?:\.(\d+)|)' - - # Thunderbird - - regex: '(Thunderbird)/(\d+)\.(\d+)(?:\.(\d+(?:pre|))|)' - family_replacement: 'Thunderbird' - - # Postbox - - regex: '(Postbox)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Postbox' - - # Barca - - regex: '(Barca(?:Pro)?)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Barca' - - # Lotus Notes - - regex: '(Lotus-Notes)/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Lotus Notes' - - # Superhuman Mail Client - # @ref: https://www.superhuman.com - - regex: 'Superhuman' - family_replacement: 'Superhuman' - - # Vivaldi uses "Vivaldi" - - regex: '(Vivaldi)/(\d+)\.(\d+)\.(\d+)' - - # Edge/major_version.minor_version - # Edge with chromium Edg/major_version.minor_version.patch.minor_patch - - regex: '(Edge?)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|)' - family_replacement: 'Edge' - - # Brave Browser https://brave.com/ - - regex: '(brave)/(\d+)\.(\d+)\.(\d+) Chrome' - family_replacement: 'Brave' - - # Iron Browser ~since version 50 - - regex: '(Chrome)/(\d+)\.(\d+)\.(\d+)[\d.]{0,100} Iron[^/]' - family_replacement: 'Iron' - - # Dolphin Browser - # @ref: http://www.dolphin.com + - regex: Microsoft Office (Word) 2014 + - regex: Outlook-Express\/7\.0 + family_replacement: Windows Live Mail + - regex: (Airmail) (\d+)\.(\d+)(?:\.(\d+)|) + - regex: (Thunderbird)/(\d+)\.(\d+)(?:\.(\d+(?:pre|))|) + family_replacement: Thunderbird + - regex: (Postbox)/(\d+)\.(\d+)\.(\d+) + family_replacement: Postbox + - regex: (Barca(?:Pro)?)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Barca + - regex: (Lotus-Notes)/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Lotus Notes + - regex: Superhuman + family_replacement: Superhuman + - regex: (Vivaldi)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (Edge?)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)(?:\.(\d+)|) + family_replacement: Edge + - regex: (Chrome)/(\d+)\.(\d+)\.(\d+)[\d.]{0,100} Iron[^/] + family_replacement: Iron - regex: '\b(Dolphin)(?: |HDCN/|/INT\-)(\d+)\.(\d+)(?:\.(\d+)|)' - - # Headless Chrome - # https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md - - regex: '(HeadlessChrome)(?:/(\d+)\.(\d+)\.(\d+)|)' - - # Evolution Mail CardDav/CalDav integration - - regex: '(Evolution)/(\d+)\.(\d+)\.(\d+\.\d+)' - - # Roundcube Mail CardDav plugin - - regex: '(RCM CardDAV plugin)/(\d+)\.(\d+)\.(\d+(?:-dev|))' - - # Browser/major_version.minor_version - - regex: '(bingbot|Bolt|AdobeAIR|Jasmine|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|NetFront|Netfront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|iTunes|MacAppStore|NetNewsWire|Space Bison|Stainless|Orca|Dolfin|BOLT|Minimo|Tizen Browser|Polaris|Abrowser|Planetweb|ICE Browser|mDolphin|qutebrowser|Otter|QupZilla|MailBar|kmail2|YahooMobileMail|ExchangeWebServices|ExchangeServicesClient|Dragon|Outlook-iOS-Android)/(\d+)\.(\d+)(?:\.(\d+)|)' - - # Chrome/Chromium/major_version.minor_version - - regex: '(Chromium|Chrome)/(\d+)\.(\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - - ########## - # IE Mobile needs to happen before Android to catch cases such as: - # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920)... - # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; ANZ821)... - # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; Orange)... - # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; Vodafone)... - ########## - - # IE Mobile - - regex: '(IEMobile)[ /](\d+)\.(\d+)' - family_replacement: 'IE Mobile' - - # Baca Berita App News Reader - - regex: '(BacaBerita App)\/(\d+)\.(\d+)\.(\d+)' - - # Podcast catchers - - regex: '^(bPod|Pocket Casts|Player FM)$' - - regex: '^(AlexaMediaPlayer|VLC)/(\d+)\.(\d+)\.([^.\s]+)' - - regex: '^(AntennaPod|WMPlayer|Zune|Podkicker|Radio|ExoPlayerDemo|Overcast|PocketTunes|NSPlayer|okhttp|DoggCatcher|QuickNews|QuickTime|Peapod|Podcasts|GoldenPod|VLC|Spotify|Miro|MediaGo|Juice|iPodder|gPodder|Banshee)/(\d+)\.(\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - - regex: '^(Peapod|Liferea)/([^.\s]+)\.([^.\s]+|)\.?([^.\s]+|)' - - regex: '^(bPod|Player FM) BMID/(\S+)' + - regex: (HeadlessChrome)(?:/(\d+)\.(\d+)\.(\d+)|) + - regex: (Evolution)/(\d+)\.(\d+)\.(\d+\.\d+) + - regex: (RCM CardDAV plugin)/(\d+)\.(\d+)\.(\d+(?:-dev|)) + - regex: (bingbot|Bolt|AdobeAIR|Jasmine|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera + Mini|Opera|NetFront|Netfront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|iTunes|MacAppStore|NetNewsWire|Space + Bison|Stainless|Orca|Dolfin|BOLT|Minimo|Tizen Browser|Polaris|Abrowser|Planetweb|ICE + Browser|mDolphin|qutebrowser|Otter|QupZilla|MailBar|kmail2|YahooMobileMail|ExchangeWebServices|ExchangeServicesClient|Dragon|Outlook-iOS-Android)/(\d+)\.(\d+)(?:\.(\d+)|) + - regex: (Chromium|Chrome)/(\d+)\.(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (IEMobile)[ /](\d+)\.(\d+) + family_replacement: IE Mobile + - regex: (BacaBerita App)\/(\d+)\.(\d+)\.(\d+) + - regex: ^(bPod|Pocket Casts|Player FM)$ + - regex: ^(AlexaMediaPlayer|VLC)/(\d+)\.(\d+)\.([^.\s]+) + - regex: ^(AntennaPod|WMPlayer|Zune|Podkicker|Radio|ExoPlayerDemo|Overcast|PocketTunes|NSPlayer|okhttp|DoggCatcher|QuickNews|QuickTime|Peapod|Podcasts|GoldenPod|VLC|Spotify|Miro|MediaGo|Juice|iPodder|gPodder|Banshee)/(\d+)\.(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: ^(Peapod|Liferea)/([^.\s]+)\.([^.\s]+|)\.?([^.\s]+|) + - regex: ^(bPod|Player FM) BMID/(\S+) - regex: '^(Podcast ?Addict)/v(\d+) ' - regex: '^(Podcast ?Addict) ' - family_replacement: 'PodcastAddict' - - regex: '(Replay) AV' - - regex: '(VOX) Music Player' - - regex: '(CITA) RSS Aggregator/(\d+)\.(\d+)' - - regex: '(Pocket Casts)$' - - regex: '(Player FM)$' - - regex: '(LG Player|Doppler|FancyMusic|MediaMonkey|Clementine) (\d+)\.(\d+)\.?([^.\s]+|)\.?([^.\s]+|)' - - regex: '(philpodder)/(\d+)\.(\d+)\.?([^.\s]+|)\.?([^.\s]+|)' - - regex: '(Player FM|Pocket Casts|DoggCatcher|Spotify|MediaMonkey|MediaGo|BashPodder)' - - regex: '(QuickTime)\.(\d+)\.(\d+)\.(\d+)' - - regex: '(Kinoma)(\d+)' - - regex: '(Fancy) Cloud Music (\d+)\.(\d+)' - family_replacement: 'FancyMusic' - - regex: 'EspnDownloadManager' - family_replacement: 'ESPN' + family_replacement: PodcastAddict + - regex: (Replay) AV + - regex: (VOX) Music Player + - regex: (CITA) RSS Aggregator/(\d+)\.(\d+) + - regex: (Pocket Casts)$ + - regex: (Player FM)$ + - regex: (LG Player|Doppler|FancyMusic|MediaMonkey|Clementine) (\d+)\.(\d+)\.?([^.\s]+|)\.?([^.\s]+|) + - regex: (philpodder)/(\d+)\.(\d+)\.?([^.\s]+|)\.?([^.\s]+|) + - regex: (Player FM|Pocket Casts|DoggCatcher|Spotify|MediaMonkey|MediaGo|BashPodder) + - regex: (QuickTime)\.(\d+)\.(\d+)\.(\d+) + - regex: (Kinoma)(\d+) + - regex: (Fancy) Cloud Music (\d+)\.(\d+) + family_replacement: FancyMusic + - regex: EspnDownloadManager + family_replacement: ESPN - regex: '(ESPN) Radio (\d+)\.(\d+)(?:\.(\d+)|) ?(?:rv:(\d+)|) ' - - regex: '(podracer|jPodder) v ?(\d+)\.(\d+)(?:\.(\d+)|)' - - regex: '(ZDM)/(\d+)\.(\d+)[; ]?' - - regex: '(Zune|BeyondPod) (\d+)(?:\.(\d+)|)[\);]' - - regex: '(WMPlayer)/(\d+)\.(\d+)\.(\d+)\.(\d+)' - - regex: '^(Lavf)' - family_replacement: 'WMPlayer' - - regex: '^(RSSRadio)[ /]?(\d+|)' - - regex: '(RSS_Radio) (\d+)\.(\d+)' - family_replacement: 'RSSRadio' - - regex: '(Podkicker) \S+/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Podkicker' - - regex: '^(HTC) Streaming Player \S+ / \S+ / \S+ / (\d+)\.(\d+)(?:\.(\d+)|)' - - regex: '^(Stitcher)/iOS' - - regex: '^(Stitcher)/Android' - - regex: '^(VLC) .{0,200}version (\d+)\.(\d+)\.(\d+)' + - regex: (podracer|jPodder) v ?(\d+)\.(\d+)(?:\.(\d+)|) + - regex: (ZDM)/(\d+)\.(\d+)[; ]? + - regex: (Zune|BeyondPod) (\d+)(?:\.(\d+)|)[\);] + - regex: (WMPlayer)/(\d+)\.(\d+)\.(\d+)\.(\d+) + - regex: ^(Lavf) + family_replacement: WMPlayer + - regex: ^(RSSRadio)[ /]?(\d+|) + - regex: (RSS_Radio) (\d+)\.(\d+) + family_replacement: RSSRadio + - regex: (Podkicker) \S+/(\d+)\.(\d+)\.(\d+) + family_replacement: Podkicker + - regex: ^(HTC) Streaming Player \S+ / \S+ / \S+ / (\d+)\.(\d+)(?:\.(\d+)|) + - regex: ^(Stitcher)/iOS + - regex: ^(Stitcher)/Android + - regex: ^(VLC) .{0,200}version (\d+)\.(\d+)\.(\d+) - regex: ' (VLC) for' - - regex: '(vlc)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'VLC' - - regex: '^(foobar)\S{1,10}/(\d+)\.(\d+|)\.?([\da-z]+|)' - - regex: '^(Clementine)\S{1,10} (\d+)\.(\d+|)\.?(\d+|)' - - regex: '(amarok)/(\d+)\.(\d+|)\.?(\d+|)' - family_replacement: 'Amarok' - - regex: '(Custom)-Feed Reader' - - # Browser major_version.minor_version.beta_version (space instead of slash) - - regex: '(iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) (\d+)\.(\d+)\.(\d+)' - # Browser major_version.minor_version (space instead of slash) - - regex: '(iCab|Lunascape|Opera|Android|Jasmine|Polaris|Microsoft SkyDriveSync|The Bat!) (\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - - # Kindle WebKit - - regex: '(Kindle)/(\d+)\.(\d+)' - - # weird android UAs - - regex: '(Android) Donut' + - regex: (vlc)/(\d+)\.(\d+)\.(\d+) + family_replacement: VLC + - regex: ^(foobar)\S{1,10}/(\d+)\.(\d+|)\.?([\da-z]+|) + - regex: ^(Clementine)\S{1,10} (\d+)\.(\d+|)\.?(\d+|) + - regex: (amarok)/(\d+)\.(\d+|)\.?(\d+|) + family_replacement: Amarok + - regex: (Custom)-Feed Reader + - regex: (iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) + (\d+)\.(\d+)\.(\d+) + - regex: (iCab|Lunascape|Opera|Android|Jasmine|Polaris|Microsoft SkyDriveSync|The + Bat!) (\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (Kindle)/(\d+)\.(\d+) + - regex: (Android) Donut v1_replacement: '1' v2_replacement: '2' - - - regex: '(Android) Eclair' + - regex: (Android) Eclair v1_replacement: '2' v2_replacement: '1' - - - regex: '(Android) Froyo' + - regex: (Android) Froyo v1_replacement: '2' v2_replacement: '2' - - - regex: '(Android) Gingerbread' + - regex: (Android) Gingerbread v1_replacement: '2' v2_replacement: '3' - - - regex: '(Android) Honeycomb' + - regex: (Android) Honeycomb v1_replacement: '3' - - # desktop mode - # http://www.anandtech.com/show/3982/windows-phone-7-review - - regex: '(MSIE) (\d+)\.(\d+).{0,100}XBLWP7' - family_replacement: 'IE Large Screen' - - # Nextcloud desktop sync client - - regex: '(Nextcloud)' - - # Generic mirall client - - regex: '(mirall)/(\d+)\.(\d+)\.(\d+)' - - # Nextcloud/Owncloud android client - - regex: '(ownCloud-android)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Owncloud' - - # Skype for Business - - regex: '(OC)/(\d+)\.(\d+)\.(\d+)\.(\d+) \(Skype for Business\)' - family_replacement: 'Skype' - - # OpenVAS Scanner - - regex: '(OpenVAS)(?:-VT)?(?:[ \/](\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)' - family_replacement: 'OpenVAS Scanner' - - # AnyConnect - - regex: '(AnyConnect)\/(\d+)(?:\.(\d+)(?:\.(\d+)|)|)' - - # Monitis - - regex: 'compatible; monitis' - family_replacement: 'Monitis' - - #### END MAIN CASES #### - - #### SPECIAL CASES #### - - regex: '(Obigo)InternetBrowser' - - regex: '(Obigo)\-Browser' - - regex: '(Obigo|OBIGO)[^\d]*(\d+)(?:.(\d+)|)' - family_replacement: 'Obigo' - - - regex: '(MAXTHON|Maxthon) (\d+)\.(\d+)' - family_replacement: 'Maxthon' - - regex: '(Maxthon|MyIE2|Uzbl|Shiira)' + - regex: (MSIE) (\d+)\.(\d+).{0,100}XBLWP7 + family_replacement: IE Large Screen + - regex: (Nextcloud) + - regex: (mirall)/(\d+)\.(\d+)\.(\d+) + - regex: (ownCloud-android)/(\d+)\.(\d+)\.(\d+) + family_replacement: Owncloud + - regex: (OC)/(\d+)\.(\d+)\.(\d+)\.(\d+) \(Skype for Business\) + family_replacement: Skype + - regex: (OpenVAS)(?:-VT)?(?:[ \/](\d+)(?:\.(\d+)|)(?:\.(\d+)|)|) + family_replacement: OpenVAS Scanner + - regex: (AnyConnect)\/(\d+)(?:\.(\d+)(?:\.(\d+)|)|) + - regex: compatible; monitis + family_replacement: Monitis + - regex: (Obigo)InternetBrowser + - regex: (Obigo)\-Browser + - regex: (Obigo|OBIGO)[^\d]*(\d+)(?:.(\d+)|) + family_replacement: Obigo + - regex: (MAXTHON|Maxthon) (\d+)\.(\d+) + family_replacement: Maxthon + - regex: (Maxthon|MyIE2|Uzbl|Shiira) v1_replacement: '0' - - - regex: '(BrowseX) \((\d+)\.(\d+)\.(\d+)' - - - regex: '(NCSA_Mosaic)/(\d+)\.(\d+)' - family_replacement: 'NCSA Mosaic' - - # Polaris/d.d is above - - regex: '(POLARIS)/(\d+)\.(\d+)' - family_replacement: 'Polaris' - - regex: '(Embider)/(\d+)\.(\d+)' - family_replacement: 'Polaris' - - - regex: '(BonEcho)/(\d+)\.(\d+)\.?([ab]?\d+|)' - family_replacement: 'Bon Echo' - - # topbuzz on IOS - - regex: '(TopBuzz) com.alex.NewsMaster/(\d+).(\d+).(\d+)' - family_replacement: 'TopBuzz' - - regex: '(TopBuzz) com.mobilesrepublic.newsrepublic/(\d+).(\d+).(\d+)' - family_replacement: 'TopBuzz' - - regex: '(TopBuzz) com.topbuzz.videoen/(\d+).(\d+).(\d+)' - family_replacement: 'TopBuzz' - - # @note: iOS / OSX Applications - - regex: '(iPod|iPhone|iPad).{1,200}GSA/(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|) Mobile' - family_replacement: 'Google' - - regex: '(iPod|iPhone|iPad).{1,200}Version/(\d+)\.(\d+)(?:\.(\d+)|).{1,200}[ +]Safari' - family_replacement: 'Mobile Safari' - - regex: '(iPod|iPod touch|iPhone|iPad);.{0,30}CPU.{0,30}OS[ +](\d+)_(\d+)(?:_(\d+)|).{0,30} AppleNews\/\d+\.\d+(?:\.\d+|)' - family_replacement: 'Mobile Safari UI/WKWebView' - - regex: '(iPod|iPhone|iPad).{1,200}Version/(\d+)\.(\d+)(?:\.(\d+)|)' - family_replacement: 'Mobile Safari UI/WKWebView' - - regex: '(iPod|iPod touch|iPhone|iPad).{0,200} Safari' - family_replacement: 'Mobile Safari' - - regex: '(iPod|iPod touch|iPhone|iPad)' - family_replacement: 'Mobile Safari UI/WKWebView' - - regex: '(Watch)(\d+),(\d+)' - family_replacement: 'Apple $1 App' - - ########################## - # Outlook on iOS >= 2.62.0 - ########################## - - regex: '(Outlook-iOS)/\d+\.\d+\.prod\.iphone \((\d+)\.(\d+)\.(\d+)\)' - - - regex: '(AvantGo) (\d+).(\d+)' - - - regex: '(OneBrowser)/(\d+).(\d+)' - family_replacement: 'ONE Browser' - - - regex: '(Avant)' + - regex: (BrowseX) \((\d+)\.(\d+)\.(\d+) + - regex: (NCSA_Mosaic)/(\d+)\.(\d+) + family_replacement: NCSA Mosaic + - regex: (POLARIS)/(\d+)\.(\d+) + family_replacement: Polaris + - regex: (Embider)/(\d+)\.(\d+) + family_replacement: Polaris + - regex: (BonEcho)/(\d+)\.(\d+)\.?([ab]?\d+|) + family_replacement: Bon Echo + - regex: (TopBuzz) com.alex.NewsMaster/(\d+).(\d+).(\d+) + family_replacement: TopBuzz + - regex: (TopBuzz) com.mobilesrepublic.newsrepublic/(\d+).(\d+).(\d+) + family_replacement: TopBuzz + - regex: (TopBuzz) com.topbuzz.videoen/(\d+).(\d+).(\d+) + family_replacement: TopBuzz + - regex: (iPod|iPhone|iPad).{1,200}GSA/(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|) Mobile + family_replacement: Google + - regex: (iPod|iPhone|iPad).{1,200}Version/(\d+)\.(\d+)(?:\.(\d+)|).{1,200}[ +]Safari + family_replacement: Mobile Safari + - regex: (iPod|iPod touch|iPhone|iPad);.{0,30}CPU.{0,30}OS[ +](\d+)_(\d+)(?:_(\d+)|).{0,30} + AppleNews\/\d+\.\d+(?:\.\d+|) + family_replacement: Mobile Safari UI/WKWebView + - regex: (iPod|iPhone|iPad).{1,200}Version/(\d+)\.(\d+)(?:\.(\d+)|) + family_replacement: Mobile Safari UI/WKWebView + - regex: (iPod|iPod touch|iPhone|iPad).{0,200} Safari + family_replacement: Mobile Safari + - regex: (iPod|iPod touch|iPhone|iPad) + family_replacement: Mobile Safari UI/WKWebView + - regex: (Watch)(\d+),(\d+) + family_replacement: Apple $1 App + - regex: (Outlook-iOS)/\d+\.\d+\.prod\.iphone \((\d+)\.(\d+)\.(\d+)\) + - regex: (AvantGo) (\d+).(\d+) + - regex: (OneBrowser)/(\d+).(\d+) + family_replacement: ONE Browser + - regex: (Avant) v1_replacement: '1' - - # This is the Tesla Model S (see similar entry in device parsers) - - regex: '(QtCarBrowser)' + - regex: (QtCarBrowser) v1_replacement: '1' - - - regex: '^(iBrowser/Mini)(\d+).(\d+)' - family_replacement: 'iBrowser Mini' - - regex: '^(iBrowser|iRAPP)/(\d+).(\d+)' - - # nokia browsers - # based on: http://www.developer.nokia.com/Community/Wiki/User-Agent_headers_for_Nokia_devices - - regex: '^(Nokia)' - family_replacement: 'Nokia Services (WAP) Browser' - - regex: '(NokiaBrowser)/(\d+)\.(\d+).(\d+)\.(\d+)' - family_replacement: 'Nokia Browser' - - regex: '(NokiaBrowser)/(\d+)\.(\d+).(\d+)' - family_replacement: 'Nokia Browser' - - regex: '(NokiaBrowser)/(\d+)\.(\d+)' - family_replacement: 'Nokia Browser' - - regex: '(BrowserNG)/(\d+)\.(\d+).(\d+)' - family_replacement: 'Nokia Browser' - - regex: '(Series60)/5\.0' - family_replacement: 'Nokia Browser' + - regex: ^(iBrowser/Mini)(\d+).(\d+) + family_replacement: iBrowser Mini + - regex: ^(iBrowser|iRAPP)/(\d+).(\d+) + - regex: ^(Nokia) + family_replacement: Nokia Services (WAP) Browser + - regex: (NokiaBrowser)/(\d+)\.(\d+).(\d+)\.(\d+) + family_replacement: Nokia Browser + - regex: (NokiaBrowser)/(\d+)\.(\d+).(\d+) + family_replacement: Nokia Browser + - regex: (NokiaBrowser)/(\d+)\.(\d+) + family_replacement: Nokia Browser + - regex: (BrowserNG)/(\d+)\.(\d+).(\d+) + family_replacement: Nokia Browser + - regex: (Series60)/5\.0 + family_replacement: Nokia Browser v1_replacement: '7' v2_replacement: '0' - - regex: '(Series60)/(\d+)\.(\d+)' - family_replacement: 'Nokia OSS Browser' - - regex: '(S40OviBrowser)/(\d+)\.(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Ovi Browser' - - regex: '(Nokia)[EN]?(\d+)' - - # BlackBerry devices - - regex: '(PlayBook).{1,200}RIM Tablet OS (\d+)\.(\d+)\.(\d+)' - family_replacement: 'BlackBerry WebKit' - - regex: '(Black[bB]erry|BB10).{1,200}Version/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'BlackBerry WebKit' - - regex: '(Black[bB]erry)\s?(\d+)' - family_replacement: 'BlackBerry' - - - regex: '(OmniWeb)/v(\d+)\.(\d+)' - - - regex: '(Blazer)/(\d+)\.(\d+)' - family_replacement: 'Palm Blazer' - - - regex: '(Pre)/(\d+)\.(\d+)' - family_replacement: 'Palm Pre' - - # fork of Links - - regex: '(ELinks)/(\d+)\.(\d+)' - - regex: '(ELinks) \((\d+)\.(\d+)' - - regex: '(Links) \((\d+)\.(\d+)' - - - regex: '(QtWeb) Internet Browser/(\d+)\.(\d+)' - - # Phantomjs, should go before Safari - - regex: '(PhantomJS)/(\d+)\.(\d+)\.(\d+)' - - # WebKit Nightly - - regex: '(AppleWebKit)/(\d+)(?:\.(\d+)|)\+ .{0,200} Safari' - family_replacement: 'WebKit Nightly' - - # Safari - - regex: '(Version)/(\d+)\.(\d+)(?:\.(\d+)|).{0,100}Safari/' - family_replacement: 'Safari' - # Safari didn't provide "Version/d.d.d" prior to 3.0 - - regex: '(Safari)/\d+' - - - regex: '(OLPC)/Update(\d+)\.(\d+)' - - - regex: '(OLPC)/Update()\.(\d+)' + - regex: (Series60)/(\d+)\.(\d+) + family_replacement: Nokia OSS Browser + - regex: (S40OviBrowser)/(\d+)\.(\d+)\.(\d+)\.(\d+) + family_replacement: Ovi Browser + - regex: (Nokia)[EN]?(\d+) + - regex: (PlayBook).{1,200}RIM Tablet OS (\d+)\.(\d+)\.(\d+) + family_replacement: BlackBerry WebKit + - regex: (Black[bB]erry|BB10).{1,200}Version/(\d+)\.(\d+)\.(\d+) + family_replacement: BlackBerry WebKit + - regex: (Black[bB]erry)\s?(\d+) + family_replacement: BlackBerry + - regex: (OmniWeb)/v(\d+)\.(\d+) + - regex: (Blazer)/(\d+)\.(\d+) + family_replacement: Palm Blazer + - regex: (Pre)/(\d+)\.(\d+) + family_replacement: Palm Pre + - regex: (ELinks)/(\d+)\.(\d+) + - regex: (ELinks) \((\d+)\.(\d+) + - regex: (Links) \((\d+)\.(\d+) + - regex: (QtWeb) Internet Browser/(\d+)\.(\d+) + - regex: (PhantomJS)/(\d+)\.(\d+)\.(\d+) + - regex: (AppleWebKit)/(\d+)(?:\.(\d+)|)\+ .{0,200} Safari + family_replacement: WebKit Nightly + - regex: (Version)/(\d+)\.(\d+)(?:\.(\d+)|).{0,100}Safari/ + family_replacement: Safari + - regex: (Safari)/\d+ + - regex: (OLPC)/Update(\d+)\.(\d+) + - regex: (OLPC)/Update()\.(\d+) v1_replacement: '0' - - - regex: '(SEMC\-Browser)/(\d+)\.(\d+)' - - - regex: '(Teleca)' - family_replacement: 'Teleca Browser' - - - regex: '(Phantom)/V(\d+)\.(\d+)' - family_replacement: 'Phantom Browser' - - - regex: '(Trident)/(7|8)\.(0)' - family_replacement: 'IE' + - regex: (SEMC\-Browser)/(\d+)\.(\d+) + - regex: (Teleca) + family_replacement: Teleca Browser + - regex: (Phantom)/V(\d+)\.(\d+) + family_replacement: Phantom Browser + - regex: (Trident)/(7|8)\.(0) + family_replacement: IE v1_replacement: '11' - - - regex: '(Trident)/(6)\.(0)' - family_replacement: 'IE' + - regex: (Trident)/(6)\.(0) + family_replacement: IE v1_replacement: '10' - - - regex: '(Trident)/(5)\.(0)' - family_replacement: 'IE' + - regex: (Trident)/(5)\.(0) + family_replacement: IE v1_replacement: '9' - - - regex: '(Trident)/(4)\.(0)' - family_replacement: 'IE' + - regex: (Trident)/(4)\.(0) + family_replacement: IE v1_replacement: '8' - - # Espial - - regex: '(Espial)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - - # Apple Mail - - # apple mail - not directly detectable, have it after Safari stuff - - regex: '(AppleWebKit)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Apple Mail' - - # AFTER THE EDGE CASES ABOVE! - # AFTER IE11 - # BEFORE all other IE - - regex: '(Firefox)/(\d+)\.(\d+)(?:\.(\d+)|$)' - - regex: '(Firefox)/(\d+)\.(\d+)(pre|[ab]\d+[a-z]*|)' - - - - regex: '([MS]?IE) (\d+)\.(\d+)' - family_replacement: 'IE' - - - regex: '(python-requests)/(\d+)\.(\d+)' - family_replacement: 'Python Requests' - - # headless user-agents - - regex: '\b(Windows-Update-Agent|WindowsPowerShell|Microsoft-CryptoAPI|SophosUpdateManager|SophosAgent|Debian APT-HTTP|Ubuntu APT-HTTP|libcurl-agent|libwww-perl|urlgrabber|curl|PycURL|Wget|wget2|aria2|Axel|OpenBSD ftp|lftp|jupdate|insomnia|fetch libfetch|akka-http|got|CloudCockpitBackend|ReactorNetty|axios|Jersey|Vert.x-WebClient|Apache-CXF|Go-CF-client|go-resty|AHC|HTTPie)(?:[ /](\d+)(?:\.(\d+)|)(?:\.(\d+)|)|)' - - # CloudFoundry - - regex: '^(cf)\/(\d+)\.(\d+)\.(\S+)' - family_replacement: 'CloudFoundry' - - # SAP Leonardo - - regex: '^(sap-leonardo-iot-sdk-nodejs) \/ (\d+)\.(\d+)\.(\d+)' - - # SAP Netweaver Application Server - - regex: '^(SAP NetWeaver Application Server) \(1.0;(\d{1})(\d{2})\)' - - # HttpClient - - regex: '^(\w+-HTTPClient)\/(\d+)\.(\d+)-(\S+)' - family_replacement: 'HTTPClient' - - # go-cli - - regex: '^(go-cli)\s(\d+)\.(\d+).(\S+)' - - # Other Clients with the pattern /[v].[.] - - regex: '^(Java-EurekaClient|Java-EurekaClient-Replication|HTTPClient|lua-resty-http)\/v?(\d+)\.(\d+)\.?(\d*)' - - ## Clints with the pattern - - regex: '^(ping-service|sap xsuaa|Node-oauth|Site24x7|SAP CPI|JAEGER_SECURITY)' - - # Asynchronous HTTP Client/Server for asyncio and Python (https://aiohttp.readthedocs.io/) - - regex: '(Python/3\.\d{1,3} aiohttp)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Python aiohttp' - - - regex: '(Java)[/ ]?\d{1}\.(\d+)\.(\d+)[_-]*([a-zA-Z0-9]+|)' - - - regex: '(Java)[/ ]?(\d+)\.(\d+)\.(\d+)' - - # minio-go (https://github.com/minio/minio-go) - - regex: '(minio-go)/v(\d+)\.(\d+)\.(\d+)' - - # ureq - minimal request library in rust (https://github.com/algesten/ureq) - - regex: '^(ureq)[/ ](\d+)\.(\d+).(\d+)' - - # http.rb - HTTP (The Gem! a.k.a. http.rb) - a fast Ruby HTTP client - # (https://github.com/httprb/http/blob/3aa7470288deb81f7d7b982c1e2381871049dcbb/lib/http/request.rb#L27) - - regex: '^(http\.rb)/(\d+)\.(\d+).(\d+)' - - # Guzzle, PHP HTTP client (https://docs.guzzlephp.org/) - - regex: '^(GuzzleHttp)/(\d+)\.(\d+).(\d+)' - - # lorien/grab - Web Scraping Framework (https://github.com/lorien/grab) - - regex: '^(grab)\b' - - # Cloud Storage Clients - - regex: '^(Cyberduck)/(\d+)\.(\d+)\.(\d+)(?:\.\d+|)' - - regex: '^(S3 Browser) (\d+)[.-](\d+)[.-](\d+)(?:\s*https?://s3browser\.com|)' - - regex: '(S3Gof3r)' - # IBM COS (Cloud Object Storage) API - - regex: '\b(ibm-cos-sdk-(?:core|java|js|python))/(\d+)\.(\d+)(?:\.(\d+)|)' - # rusoto - Rusoto - AWS SDK for Rust - https://github.com/rusoto/rusoto - - regex: '^(rusoto)/(\d+)\.(\d+)\.(\d+)' - # rclone - rsync for cloud storage - https://rclone.org/ - - regex: '^(rclone)/v(\d+)\.(\d+)' - - # Roku Digital-Video-Players https://www.roku.com/ - - regex: '^(Roku)/DVP-(\d+)\.(\d+)' - - # Kurio App News Reader https://kurio.co.id/ - - regex: '(Kurio)\/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'Kurio App' - - # Box Drive and Box Sync https://www.box.com/resources/downloads + - regex: (Espial)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (AppleWebKit)/(\d+)\.(\d+)\.(\d+) + family_replacement: Apple Mail + - regex: (Firefox)/(\d+)\.(\d+)(?:\.(\d+)|$) + - regex: (Firefox)/(\d+)\.(\d+)(pre|[ab]\d+[a-z]*|) + - regex: ([MS]?IE) (\d+)\.(\d+) + family_replacement: IE + - regex: (python-requests)/(\d+)\.(\d+) + family_replacement: Python Requests + - regex: \b(Windows-Update-Agent|WindowsPowerShell|Microsoft-CryptoAPI|SophosUpdateManager|SophosAgent|Debian + APT-HTTP|Ubuntu APT-HTTP|libcurl-agent|libwww-perl|urlgrabber|curl|PycURL|Wget|wget2|aria2|Axel|OpenBSD + ftp|lftp|jupdate|insomnia|fetch libfetch|akka-http|got|CloudCockpitBackend|ReactorNetty|axios|Jersey|Vert.x-WebClient|Apache-CXF|Go-CF-client|go-resty|AHC|HTTPie)(?:[ + /](\d+)(?:\.(\d+)|)(?:\.(\d+)|)|) + - regex: ^(cf)\/(\d+)\.(\d+)\.(\S+) + family_replacement: CloudFoundry + - regex: ^(sap-leonardo-iot-sdk-nodejs) \/ (\d+)\.(\d+)\.(\d+) + - regex: ^(SAP NetWeaver Application Server) \(1.0;(\d{1})(\d{2})\) + - regex: ^(\w+-HTTPClient)\/(\d+)\.(\d+)-(\S+) + family_replacement: HTTPClient + - regex: ^(go-cli)\s(\d+)\.(\d+).(\S+) + - regex: ^(Java-EurekaClient|Java-EurekaClient-Replication|HTTPClient|lua-resty-http)\/v?(\d+)\.(\d+)\.?(\d*) + - regex: ^(ping-service|sap xsuaa|Node-oauth|Site24x7|SAP CPI|JAEGER_SECURITY) + - regex: (Python/3\.\d{1,3} aiohttp)/(\d+)\.(\d+)\.(\d+) + family_replacement: Python aiohttp + - regex: (Java)[/ ]?\d{1}\.(\d+)\.(\d+)[_-]*([a-zA-Z0-9]+|) + - regex: (Java)[/ ]?(\d+)\.(\d+)\.(\d+) + - regex: (minio-go)/v(\d+)\.(\d+)\.(\d+) + - regex: ^(ureq)[/ ](\d+)\.(\d+).(\d+) + - regex: ^(http\.rb)/(\d+)\.(\d+).(\d+) + - regex: ^(GuzzleHttp)/(\d+)\.(\d+).(\d+) + - regex: ^(grab)\b + - regex: ^(Cyberduck)/(\d+)\.(\d+)\.(\d+)(?:\.\d+|) + - regex: ^(S3 Browser) (\d+)[.-](\d+)[.-](\d+)(?:\s*https?://s3browser\.com|) + - regex: (S3Gof3r) + - regex: \b(ibm-cos-sdk-(?:core|java|js|python))/(\d+)\.(\d+)(?:\.(\d+)|) + - regex: ^(rusoto)/(\d+)\.(\d+)\.(\d+) + - regex: ^(rclone)/v(\d+)\.(\d+) + - regex: ^(Roku)/DVP-(\d+)\.(\d+) + - regex: (Kurio)\/(\d+)\.(\d+)\.(\d+) + family_replacement: Kurio App - regex: '^(Box(?: Sync)?)/(\d+)\.(\d+)\.(\d+)' - - # ViaFree streaming app https://www.viafree.{dk|se|no} - - regex: '^(ViaFree|Viafree)-(?:tvOS-)?[A-Z]{2}/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'ViaFree' - - # Transmit (https://library.panic.com/transmit/) - - regex: '(Transmit)/(\d+)\.(\d+)\.(\d+)' - - # Download Master (https://downloadmaster.ru/) - - regex: '(Download Master)' - - # HTTrack crawler - - regex: '\b(HTTrack) (\d+)\.(\d+)(?:[\.\-](\d+)|)' - - # SerenityOS (https://serenityos.org) - # https://github.com/SerenityOS/serenity/blob/2e1bbcb0faeae92d7595b8e0b022a8cdcecca07e/Userland/Libraries/LibWeb/Loader/ResourceLoader.h#L27 - - regex: 'SerenityOS' - family_replacement: 'SerenityOS Browser' - + - regex: ^(ViaFree|Viafree)-(?:tvOS-)?[A-Z]{2}/(\d+)\.(\d+)\.(\d+) + family_replacement: ViaFree + - regex: (Transmit)/(\d+)\.(\d+)\.(\d+) + - regex: (Download Master) + - regex: \b(HTTrack) (\d+)\.(\d+)(?:[\.\-](\d+)|) + - regex: (Ladybird)\/(\d+)\.(\d+) + - regex: (MullvadBrowser)/(\d+)(?:\.(\d+)|)(?:\.(\d+)|) os_parsers: - ########## - # HbbTV vendors - ########## - - # starts with the easy one : Panasonic seems consistent across years, hope it will continue - #HbbTV/1.1.1 (;Panasonic;VIERA 2011;f.532;0071-0802 2000-0000;) - #HbbTV/1.1.1 (;Panasonic;VIERA 2012;1.261;0071-3103 2000-0000;) - #HbbTV/1.2.1 (;Panasonic;VIERA 2013;3.672;4101-0003 0002-0000;) - #- regex: 'HbbTV/\d+\.\d+\.\d+ \(;(Panasonic);VIERA ([0-9]{4});' - - # Sony is consistent too but do not place year like the other - # Opera/9.80 (Linux armv7l; HbbTV/1.1.1 (; Sony; KDL32W650A; PKG3.211EUA; 2013;); ) Presto/2.12.362 Version/12.11 - # Opera/9.80 (Linux mips; U; HbbTV/1.1.1 (; Sony; KDL40HX751; PKG1.902EUA; 2012;);; en) Presto/2.10.250 Version/11.60 - # Opera/9.80 (Linux mips; U; HbbTV/1.1.1 (; Sony; KDL22EX320; PKG4.017EUA; 2011;);; en) Presto/2.7.61 Version/11.00 - #- regex: 'HbbTV/\d+\.\d+\.\d+ \(; (Sony);.{0,200};.{0,200}; ([0-9]{4});\)' - - - # LG is consistent too, but we need to add manually the year model - #Mozilla/5.0 (Unknown; Linux armv7l) AppleWebKit/537.1+ (KHTML, like Gecko) Safari/537.1+ HbbTV/1.1.1 ( ;LGE ;NetCast 4.0 ;03.20.30 ;1.0M ;) - #Mozilla/5.0 (DirectFB; Linux armv7l) AppleWebKit/534.26+ (KHTML, like Gecko) Version/5.0 Safari/534.26+ HbbTV/1.1.1 ( ;LGE ;NetCast 3.0 ;1.0 ;1.0M ;) - - regex: 'HbbTV/\d+\.\d+\.\d+ \( ;(LG)E ;NetCast 4.0' + - regex: HbbTV/\d+\.\d+\.\d+ \( ;(LG)E ;NetCast 4.0 os_v1_replacement: '2013' - - regex: 'HbbTV/\d+\.\d+\.\d+ \( ;(LG)E ;NetCast 3.0' + - regex: HbbTV/\d+\.\d+\.\d+ \( ;(LG)E ;NetCast 3.0 os_v1_replacement: '2012' - - # Samsung is on its way of normalizing their user-agent - # HbbTV/1.1.1 (;Samsung;SmartTV2013;T-FXPDEUC-1102.2;;) WebKit - # HbbTV/1.1.1 (;Samsung;SmartTV2013;T-MST12DEUC-1102.1;;) WebKit - # HbbTV/1.1.1 (;Samsung;SmartTV2012;;;) WebKit - # HbbTV/1.1.1 (;;;;;) Maple_2011 - - regex: 'HbbTV/1.1.1 \(;;;;;\) Maple_2011' - os_replacement: 'Samsung' + - regex: HbbTV/1.1.1 \(;;;;;\) Maple_2011 + os_replacement: Samsung os_v1_replacement: '2011' - # manage the two models of 2013 - - regex: 'HbbTV/\d+\.\d+\.\d+ \(;(Samsung);SmartTV([0-9]{4});.{0,200}FXPDEUC' - os_v2_replacement: 'UE40F7000' - - regex: 'HbbTV/\d+\.\d+\.\d+ \(;(Samsung);SmartTV([0-9]{4});.{0,200}MST12DEUC' - os_v2_replacement: 'UE32F4500' - # generic Samsung (works starting in 2012) - #- regex: 'HbbTV/\d+\.\d+\.\d+ \(;(Samsung);SmartTV([0-9]{4});' - - # Philips : not found any other way than a manual mapping - # Opera/9.80 (Linux mips; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/4.1.3 PHILIPSTV/1.1.1; en) Presto/2.10.250 Version/11.60 - # Opera/9.80 (Linux mips ; U; HbbTV/1.1.1 (; Philips; ; ; ; ) CE-HTML/1.0 NETTV/3.2.1; en) Presto/2.6.33 Version/10.70 - - regex: 'HbbTV/1\.1\.1 \(; (Philips);.{0,200}NETTV/4' + - regex: HbbTV/\d+\.\d+\.\d+ \(;(Samsung);SmartTV([0-9]{4});.{0,200}FXPDEUC + os_v2_replacement: UE40F7000 + - regex: HbbTV/\d+\.\d+\.\d+ \(;(Samsung);SmartTV([0-9]{4});.{0,200}MST12DEUC + os_v2_replacement: UE32F4500 + - regex: HbbTV/1\.1\.1 \(; (Philips);.{0,200}NETTV/4 os_v1_replacement: '2013' - - regex: 'HbbTV/1\.1\.1 \(; (Philips);.{0,200}NETTV/3' + - regex: HbbTV/1\.1\.1 \(; (Philips);.{0,200}NETTV/3 os_v1_replacement: '2012' - - regex: 'HbbTV/1\.1\.1 \(; (Philips);.{0,200}NETTV/2' + - regex: HbbTV/1\.1\.1 \(; (Philips);.{0,200}NETTV/2 os_v1_replacement: '2011' - - # the HbbTV emulator developers use HbbTV/1.1.1 (;;;;;) firetv-firefox-plugin 1.1.20 - - regex: 'HbbTV/\d+\.\d+\.\d+.{0,100}(firetv)-firefox-plugin (\d+).(\d+).(\d+)' - os_replacement: 'FireHbbTV' - - # generic HbbTV, hoping to catch manufacturer name (always after 2nd comma) and the first string that looks like a 2011-2019 year - - regex: 'HbbTV/\d+\.\d+\.\d+ \(.{0,30}; ?([a-zA-Z]+) ?;.{0,30}(201[1-9]).{0,30}\)' - - # aspiegel.com spider (owned by Huawei, later renamed PetalBot) - - regex: 'AspiegelBot|PetalBot' - os_replacement: 'Other' - - ########## - # @note: Windows Phone needs to come before Windows NT 6.1 {0,2}and* before Android to catch cases such as: - # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920)... - # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; ANZ821)... - # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; Orange)... - # Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 920; Vodafone)... - ########## - - - regex: '(Windows Phone) (?:OS[ /])?(\d+)\.(\d+)' - - # Again a MS-special one: iPhone.{0,200}Outlook-iOS-Android/x.x is erroneously detected as Android - - regex: '(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone)[ +]+(\d+)[_\.](\d+)(?:[_\.](\d+)|).{0,100}Outlook-iOS-Android' - os_replacement: 'iOS' - - # Special case for old ArcGIS Mobile products - - regex: 'ArcGIS\.?(iOS|Android)-\d+\.\d+(?:\.\d+|)(?:[^\/]{1,50}|)\/(\d+)(?:\.(\d+)(?:\.(\d+)|)|)' - - # Special case for new ArcGIS Mobile products - - regex: 'ArcGISRuntime-(?:Android|iOS)\/\d+\.\d+(?:\.\d+|) \((Android|iOS) (\d+)(?:\.(\d+)(?:\.(\d+)|)|);' - - ########## - # Android - # can actually detect rooted android os. do we care? - ########## - - regex: '(Android)[ \-/](\d+)(?:\.(\d+)|)(?:[.\-]([a-z0-9]+)|)' - - - regex: '(Android) Donut' + - regex: HbbTV/\d+\.\d+\.\d+.{0,100}(firetv)-firefox-plugin (\d+).(\d+).(\d+) + os_replacement: FireHbbTV + - regex: HbbTV/\d+\.\d+\.\d+ \(.{0,30}; ?([a-zA-Z]+) ?;.{0,30}(201[1-9]).{0,30}\) + - regex: AspiegelBot|PetalBot + os_replacement: Other + - regex: (Windows Phone) (?:OS[ /])?(\d+)\.(\d+) + - regex: (CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone)[ +]+(\d+)[_\.](\d+)(?:[_\.](\d+)|).{0,100}Outlook-iOS-Android + os_replacement: iOS + - regex: ArcGIS\.?(iOS|Android)-\d+\.\d+(?:\.\d+|)(?:[^\/]{1,50}|)\/(\d+)(?:\.(\d+)(?:\.(\d+)|)|) + - regex: ArcGISRuntime-(?:Android|iOS)\/\d+\.\d+(?:\.\d+|) \((Android|iOS) (\d+)(?:\.(\d+)(?:\.(\d+)|)|); + - regex: (Android) (\d+)(?:\.(\d+)).*CrKey + os_replacement: Chromecast Android + - regex: Fuchsia.*(CrKey)(?:[/](\d+)\.(\d+)(?:\.(\d+)|)|) + os_replacement: Chromecast Fuchsia + - regex: Linux.*(CrKey)(?:[/](\d+)\.(\d+)(?:\.(\d+)|)|).*DeviceType/SmartSpeaker + os_replacement: Chromecast SmartSpeaker + - regex: Linux.*(CrKey)(?:[/](\d+)\.(\d+)(?:\.(\d+)|)|) + os_replacement: Chromecast Linux + - regex: (Android)[ \-/](\d+)(?:\.(\d+)|)(?:[.\-]([a-z0-9]+)|) + - regex: (Android) Donut os_v1_replacement: '1' os_v2_replacement: '2' - - - regex: '(Android) Eclair' + - regex: (Android) Eclair os_v1_replacement: '2' os_v2_replacement: '1' - - - regex: '(Android) Froyo' + - regex: (Android) Froyo os_v1_replacement: '2' os_v2_replacement: '2' - - - regex: '(Android) Gingerbread' + - regex: (Android) Gingerbread os_v1_replacement: '2' os_v2_replacement: '3' - - - regex: '(Android) Honeycomb' + - regex: (Android) Honeycomb os_v1_replacement: '3' - - # Android 9; Android 10; - - regex: '(Android) (\d+);' + - regex: (Android) (\d+); - regex: '(Android): (\d+)(?:\.(\d+)(?:\.(\d+)|)|);' - - # UCWEB - - regex: '^UCWEB.{0,200}; (Adr) (\d+)\.(\d+)(?:[.\-]([a-z0-9]{1,100})|);' - os_replacement: 'Android' - - regex: '^UCWEB.{0,200}; (iPad|iPh|iPd) OS (\d+)_(\d+)(?:_(\d+)|);' - os_replacement: 'iOS' - - regex: '^UCWEB.{0,200}; (wds) (\d+)\.(\d+)(?:\.(\d+)|);' - os_replacement: 'Windows Phone' - # JUC - - regex: '^(JUC).{0,200}; ?U; ?(?:Android|)(\d+)\.(\d+)(?:[\.\-]([a-z0-9]{1,100})|)' - os_replacement: 'Android' - - # Salesforce - - regex: '(android)\s(?:mobile\/)(\d+)(?:\.(\d+)(?:\.(\d+)|)|)' - os_replacement: 'Android' - - ########## - # Meta Quest - ########## - - regex: 'Quest' - os_replacement: 'Android' - - ########## - # Kindle Android - ########## - - regex: '(Silk-Accelerated=[a-z]{4,5})' - os_replacement: 'Android' - - # Citrix Chrome App on Chrome OS - # Note, this needs to come before the windows parsers as the app doesn't - # properly identify as Chrome OS - # - # ex: Mozilla/5.0 (X11; Windows aarch64 10718.88.2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.118 Safari/537.36 CitrixChromeApp - - regex: '(x86_64|aarch64)\ (\d+)\.(\d+)\.(\d+).{0,100}Chrome.{0,100}(?:CitrixChromeApp)$' - os_replacement: 'Chrome OS' - - ########## - # Windows - # http://en.wikipedia.org/wiki/Windows_NT#Releases - # possibility of false positive when different marketing names share same NT kernel - # e.g. windows server 2003 and windows xp - # lots of ua strings have Windows NT 4.1 !?!?!?!? !?!? !? !????!?! !!! ??? !?!?! ? - # (very) roughly ordered in terms of frequency of occurence of regex (win xp currently most frequent, etc) - ########## - - # ie mobile desktop mode - # spoofs nt 6.1. must come before windows 7 - - regex: '(XBLWP7)' - os_replacement: 'Windows Phone' - - # @note: This needs to come before Windows NT 6.1 - - regex: '(Windows ?Mobile)' - os_replacement: 'Windows Mobile' - - - regex: '(Windows 10)' - os_replacement: 'Windows' + - regex: ^UCWEB.{0,200}; (Adr) (\d+)\.(\d+)(?:[.\-]([a-z0-9]{1,100})|); + os_replacement: Android + - regex: ^UCWEB.{0,200}; (iPad|iPh|iPd) OS (\d+)_(\d+)(?:_(\d+)|); + os_replacement: iOS + - regex: ^UCWEB.{0,200}; (wds) (\d+)\.(\d+)(?:\.(\d+)|); + os_replacement: Windows Phone + - regex: ^(JUC).{0,200}; ?U; ?(?:Android|)(\d+)\.(\d+)(?:[\.\-]([a-z0-9]{1,100})|) + os_replacement: Android + - regex: (android)\s(?:mobile\/)(\d+)(?:\.(\d+)(?:\.(\d+)|)|) + os_replacement: Android + - regex: Quest + os_replacement: Android + - regex: (Silk-Accelerated=[a-z]{4,5}) + os_replacement: Android + - regex: (x86_64|aarch64)\ (\d+)\.(\d+)\.(\d+).{0,100}Chrome.{0,100}(?:CitrixChromeApp)$ + os_replacement: Chrome OS + - regex: (XBLWP7) + os_replacement: Windows Phone + - regex: (Windows ?Mobile) + os_replacement: Windows Mobile + - regex: (Windows 10) + os_replacement: Windows os_v1_replacement: '10' - - - regex: '(Windows (?:NT 5\.2|NT 5\.1))' - os_replacement: 'Windows' - os_v1_replacement: 'XP' - - - regex: '(Win(?:dows NT |32NT\/)6\.1)' - os_replacement: 'Windows' + - regex: (Windows (?:NT 5\.2|NT 5\.1)) + os_replacement: Windows + os_v1_replacement: XP + - regex: (Win(?:dows NT |32NT\/)6\.1) + os_replacement: Windows os_v1_replacement: '7' - - - regex: '(Win(?:dows NT |32NT\/)6\.0)' - os_replacement: 'Windows' - os_v1_replacement: 'Vista' - - - regex: '(Win 9x 4\.90)' - os_replacement: 'Windows' - os_v1_replacement: 'ME' - - - regex: '(Windows NT 6\.2; ARM;)' - os_replacement: 'Windows' - os_v1_replacement: 'RT' - - - regex: '(Win(?:dows NT |32NT\/)6\.2)' - os_replacement: 'Windows' + - regex: (Win(?:dows NT |32NT\/)6\.0) + os_replacement: Windows + os_v1_replacement: Vista + - regex: (Win 9x 4\.90) + os_replacement: Windows + os_v1_replacement: ME + - regex: (Windows NT 6\.2; ARM;) + os_replacement: Windows + os_v1_replacement: RT + - regex: (Win(?:dows NT |32NT\/)6\.2) + os_replacement: Windows os_v1_replacement: '8' - - - regex: '(Windows NT 6\.3; ARM;)' - os_replacement: 'Windows' - os_v1_replacement: 'RT 8' + - regex: (Windows NT 6\.3; ARM;) + os_replacement: Windows + os_v1_replacement: RT 8 os_v2_replacement: '1' - - - regex: '(Win(?:dows NT |32NT\/)6\.3)' - os_replacement: 'Windows' + - regex: (Win(?:dows NT |32NT\/)6\.3) + os_replacement: Windows os_v1_replacement: '8' os_v2_replacement: '1' - - - regex: '(Win(?:dows NT |32NT\/)6\.4)' - os_replacement: 'Windows' + - regex: (Win(?:dows NT |32NT\/)6\.4) + os_replacement: Windows os_v1_replacement: '10' - - - regex: '(Windows NT 10\.0)' - os_replacement: 'Windows' + - regex: (Windows NT 10\.0) + os_replacement: Windows os_v1_replacement: '10' - - - regex: '(Windows NT 5\.0)' - os_replacement: 'Windows' + - regex: (Windows NT 5\.0) + os_replacement: Windows os_v1_replacement: '2000' - - - regex: '(WinNT4.0)' - os_replacement: 'Windows' - os_v1_replacement: 'NT 4.0' - - - regex: '(Windows ?CE)' - os_replacement: 'Windows' - os_v1_replacement: 'CE' - - - regex: 'Win(?:dows)? ?(95|98|3.1|NT|ME|2000|XP|Vista|7|CE)' - os_replacement: 'Windows' - os_v1_replacement: '$1' - - - regex: 'Win16' - os_replacement: 'Windows' + - regex: (WinNT4.0) + os_replacement: Windows + os_v1_replacement: NT 4.0 + - regex: (Windows ?CE) + os_replacement: Windows + os_v1_replacement: CE + - regex: Win(?:dows)? ?(95|98|3.1|NT|ME|2000|XP|Vista|7|CE) + os_replacement: Windows + os_v1_replacement: $1 + - regex: Win16 + os_replacement: Windows os_v1_replacement: '3.1' - - - regex: 'Win32' - os_replacement: 'Windows' + - regex: Win32 + os_replacement: Windows os_v1_replacement: '95' - - # Box apps (Drive, Sync, Notes) on Windows https://www.box.com/resources/downloads - - regex: '^Box.{0,200}Windows/([\d.]+);' - os_replacement: 'Windows' - os_v1_replacement: '$1' - - ########## - # Tizen OS from Samsung - # spoofs Android so pushing it above - ########## - - regex: '(Tizen)[/ ](\d+)\.(\d+)' - - ########## - # Mac OS - # @ref: http://en.wikipedia.org/wiki/Mac_OS_X#Versions - # @ref: http://www.puredarwin.org/curious/versions - ########## - - regex: '((?:Mac[ +]?|; )OS[ +]X)[\s+/](?:(\d+)[_.](\d+)(?:[_.](\d+)|)|Mach-O)' - os_replacement: 'Mac OS X' - - regex: 'Mac OS X\s.{1,50}\s(\d+).(\d+).(\d+)' - os_replacement: 'Mac OS X' - os_v1_replacement: '$1' - os_v2_replacement: '$2' - os_v3_replacement: '$3' - # Leopard + - regex: ^Box.{0,200}Windows/([\d.]+); + os_replacement: Windows + os_v1_replacement: $1 + - regex: (Tizen)[/ ](\d+)\.(\d+) + - regex: Intel Mac OS X.+(CriOS|EdgiOS)/\d+ + os_replacement: iOS + - regex: ((?:Mac[ +]?|; )OS[ +]X)[\s+/](?:(\d+)[_.](\d+)(?:[_.](\d+)|)|Mach-O) + os_replacement: Mac OS X + - regex: Mac OS X\s.{1,50}\s(\d+).(\d+).(\d+) + os_replacement: Mac OS X + os_v1_replacement: $1 + os_v2_replacement: $2 + os_v3_replacement: $3 - regex: ' (Dar)(win)/(9).(\d+).{0,100}\((?:i386|x86_64|Power Macintosh)\)' - os_replacement: 'Mac OS X' + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '5' - # Snow Leopard - regex: ' (Dar)(win)/(10).(\d+).{0,100}\((?:i386|x86_64)\)' - os_replacement: 'Mac OS X' + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '6' - # Lion - regex: ' (Dar)(win)/(11).(\d+).{0,100}\((?:i386|x86_64)\)' - os_replacement: 'Mac OS X' + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '7' - # Mountain Lion - regex: ' (Dar)(win)/(12).(\d+).{0,100}\((?:i386|x86_64)\)' - os_replacement: 'Mac OS X' + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '8' - # Mavericks - regex: ' (Dar)(win)/(13).(\d+).{0,100}\((?:i386|x86_64)\)' - os_replacement: 'Mac OS X' + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '9' - # Yosemite is Darwin/14.x but patch versions are inconsistent in the Darwin string; - # more accurately covered by CFNetwork regexes downstream - - # IE on Mac doesn't specify version number - - regex: 'Mac_PowerPC' - os_replacement: 'Mac OS' - - # builds before tiger don't seem to specify version? - - # ios devices spoof (mac os x), so including intel/ppc prefixes - - regex: '(?:PPC|Intel) (Mac OS X)' - - # Box Drive and Box Sync on Mac OS X use OSX version numbers, not Darwin - - regex: '^Box.{0,200};(Darwin)/(10)\.(1\d)(?:\.(\d+)|)' - os_replacement: 'Mac OS X' - - ########## - # iOS - # http://en.wikipedia.org/wiki/IOS_version_history - ########## - # keep this above generic iOS, since AppleTV UAs contain 'CPU OS' - - regex: '(Apple\s?TV)(?:/(\d+)\.(\d+)|)' - os_replacement: 'ATV OS X' - - - regex: '(CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(\d+)[_\.](\d+)(?:[_\.](\d+)|)' - os_replacement: 'iOS' - - # remaining cases are mostly only opera uas, so catch opera as to not catch iphone spoofs - - regex: '(iPhone|iPad|iPod); Opera' - os_replacement: 'iOS' - - # few more stragglers - - regex: '(iPhone|iPad|iPod).{0,100}Mac OS X.{0,100}Version/(\d+)\.(\d+)' - os_replacement: 'iOS' - - # CFNetwork/Darwin - The specific CFNetwork or Darwin version determines - # whether the os maps to Mac OS, or iOS, or just Darwin. - # See: http://user-agents.me/cfnetwork-version-list - - regex: '(CFNetwork)/(5)48\.0\.3.{0,100} Darwin/11\.0\.0' - os_replacement: 'iOS' - - regex: '(CFNetwork)/(5)48\.(0)\.4.{0,100} Darwin/(1)1\.0\.0' - os_replacement: 'iOS' - - regex: '(CFNetwork)/(5)48\.(1)\.4' - os_replacement: 'iOS' - - regex: '(CFNetwork)/(4)85\.1(3)\.9' - os_replacement: 'iOS' - - regex: '(CFNetwork)/(6)09\.(1)\.4' - os_replacement: 'iOS' - - regex: '(CFNetwork)/(6)(0)9' - os_replacement: 'iOS' - - regex: '(CFNetwork)/6(7)2\.(1)\.13' - os_replacement: 'iOS' - - regex: '(CFNetwork)/6(7)2\.(1)\.(1)4' - os_replacement: 'iOS' - - regex: '(CF)(Network)/6(7)(2)\.1\.15' - os_replacement: 'iOS' + - regex: Mac_PowerPC + os_replacement: Mac OS + - regex: (?:PPC|Intel) (Mac OS X) + - regex: ^Box.{0,200};(Darwin)/(10)\.(1\d)(?:\.(\d+)|) + os_replacement: Mac OS X + - regex: (Apple\s?TV)(?:/(\d+)\.(\d+)|) + os_replacement: ATV OS X + - regex: (CPU[ +]OS|iPhone[ +]OS|CPU[ +]iPhone|CPU IPhone OS|CPU iPad OS)[ +]+(\d+)[_\.](\d+)(?:[_\.](\d+)|) + os_replacement: iOS + - regex: (iPhone|iPad|iPod); Opera + os_replacement: iOS + - regex: (iPhone|iPad|iPod).{0,100}Mac OS X.{0,100}Version/(\d+)\.(\d+) + os_replacement: iOS + - regex: (CFNetwork)/(5)48\.0\.3.{0,100} Darwin/11\.0\.0 + os_replacement: iOS + - regex: (CFNetwork)/(5)48\.(0)\.4.{0,100} Darwin/(1)1\.0\.0 + os_replacement: iOS + - regex: (CFNetwork)/(5)48\.(1)\.4 + os_replacement: iOS + - regex: (CFNetwork)/(4)85\.1(3)\.9 + os_replacement: iOS + - regex: (CFNetwork)/(6)09\.(1)\.4 + os_replacement: iOS + - regex: (CFNetwork)/(6)(0)9 + os_replacement: iOS + - regex: (CFNetwork)/6(7)2\.(1)\.13 + os_replacement: iOS + - regex: (CFNetwork)/6(7)2\.(1)\.(1)4 + os_replacement: iOS + - regex: (CF)(Network)/6(7)(2)\.1\.15 + os_replacement: iOS os_v1_replacement: '7' os_v2_replacement: '1' - - regex: '(CFNetwork)/6(7)2\.(0)\.(?:2|8)' - os_replacement: 'iOS' - - regex: '(CFNetwork)/709\.1' - os_replacement: 'iOS' + - regex: (CFNetwork)/6(7)2\.(0)\.(?:2|8) + os_replacement: iOS + - regex: (CFNetwork)/709\.1 + os_replacement: iOS os_v1_replacement: '8' - os_v2_replacement: '0.b5' - - regex: '(CF)(Network)/711\.(\d)' - os_replacement: 'iOS' + os_v2_replacement: 0.b5 + - regex: (CF)(Network)/711\.(\d) + os_replacement: iOS os_v1_replacement: '8' - - regex: '(CF)(Network)/(720)\.(\d)' - os_replacement: 'Mac OS X' + - regex: (CF)(Network)/(720)\.(\d) + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '10' - - regex: '(CF)(Network)/(760)\.(\d)' - os_replacement: 'Mac OS X' + - regex: (CF)(Network)/(760)\.(\d) + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '11' - - regex: 'CFNetwork/7.{0,100} Darwin/15\.4\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/7.{0,100} Darwin/15\.4\.\d+ + os_replacement: iOS os_v1_replacement: '9' os_v2_replacement: '3' os_v3_replacement: '1' - - regex: 'CFNetwork/7.{0,100} Darwin/15\.5\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/7.{0,100} Darwin/15\.5\.\d+ + os_replacement: iOS os_v1_replacement: '9' os_v2_replacement: '3' os_v3_replacement: '2' - - regex: 'CFNetwork/7.{0,100} Darwin/15\.6\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/7.{0,100} Darwin/15\.6\.\d+ + os_replacement: iOS os_v1_replacement: '9' os_v2_replacement: '3' os_v3_replacement: '5' - - regex: '(CF)(Network)/758\.(\d)' - os_replacement: 'iOS' + - regex: (CF)(Network)/758\.(\d) + os_replacement: iOS os_v1_replacement: '9' - - regex: 'CFNetwork/808\.3 Darwin/16\.3\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/808\.3 Darwin/16\.3\.\d+ + os_replacement: iOS os_v1_replacement: '10' os_v2_replacement: '2' os_v3_replacement: '1' - - regex: '(CF)(Network)/808\.(\d)' - os_replacement: 'iOS' + - regex: (CF)(Network)/808\.(\d) + os_replacement: iOS os_v1_replacement: '10' - - ########## - # CFNetwork macOS Apps (must be before CFNetwork iOS Apps - # @ref: https://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history - ########## - - regex: 'CFNetwork/.{0,100} Darwin/17\.\d+.{0,100}\(x86_64\)' - os_replacement: 'Mac OS X' + - regex: CFNetwork/.{0,100} Darwin/17\.\d+.{0,100}\(x86_64\) + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '13' - - regex: 'CFNetwork/.{0,100} Darwin/16\.\d+.{0,100}\(x86_64\)' - os_replacement: 'Mac OS X' + - regex: CFNetwork/.{0,100} Darwin/16\.\d+.{0,100}\(x86_64\) + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '12' - - regex: 'CFNetwork/8.{0,100} Darwin/15\.\d+.{0,100}\(x86_64\)' - os_replacement: 'Mac OS X' + - regex: CFNetwork/8.{0,100} Darwin/15\.\d+.{0,100}\(x86_64\) + os_replacement: Mac OS X os_v1_replacement: '10' os_v2_replacement: '11' - ########## - # CFNetwork iOS Apps - # @ref: https://en.wikipedia.org/wiki/Darwin_(operating_system)#Release_history - ########## - - regex: 'CFNetwork/.{0,100} Darwin/(9)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/(9)\.\d+ + os_replacement: iOS os_v1_replacement: '1' - - regex: 'CFNetwork/.{0,100} Darwin/(10)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/(10)\.\d+ + os_replacement: iOS os_v1_replacement: '4' - - regex: 'CFNetwork/.{0,100} Darwin/(11)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/(11)\.\d+ + os_replacement: iOS os_v1_replacement: '5' - - regex: 'CFNetwork/.{0,100} Darwin/(13)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/(13)\.\d+ + os_replacement: iOS os_v1_replacement: '6' - - regex: 'CFNetwork/6.{0,100} Darwin/(14)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/6.{0,100} Darwin/(14)\.\d+ + os_replacement: iOS os_v1_replacement: '7' - - regex: 'CFNetwork/7.{0,100} Darwin/(14)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/7.{0,100} Darwin/(14)\.\d+ + os_replacement: iOS os_v1_replacement: '8' os_v2_replacement: '0' - - regex: 'CFNetwork/7.{0,100} Darwin/(15)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/7.{0,100} Darwin/(15)\.\d+ + os_replacement: iOS os_v1_replacement: '9' os_v2_replacement: '0' - - regex: 'CFNetwork/8.{0,100} Darwin/16\.5\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/16\.5\.\d+ + os_replacement: iOS os_v1_replacement: '10' os_v2_replacement: '3' - - regex: 'CFNetwork/8.{0,100} Darwin/16\.6\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/16\.6\.\d+ + os_replacement: iOS os_v1_replacement: '10' os_v2_replacement: '3' os_v3_replacement: '2' - - regex: 'CFNetwork/8.{0,100} Darwin/16\.7\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/16\.7\.\d+ + os_replacement: iOS os_v1_replacement: '10' os_v2_replacement: '3' os_v3_replacement: '3' - - regex: 'CFNetwork/8.{0,100} Darwin/(16)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/(16)\.\d+ + os_replacement: iOS os_v1_replacement: '10' - - regex: 'CFNetwork/8.{0,100} Darwin/17\.0\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/17\.0\.\d+ + os_replacement: iOS os_v1_replacement: '11' os_v2_replacement: '0' - - regex: 'CFNetwork/8.{0,100} Darwin/17\.2\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/17\.2\.\d+ + os_replacement: iOS os_v1_replacement: '11' os_v2_replacement: '1' - - regex: 'CFNetwork/8.{0,100} Darwin/17\.3\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/17\.3\.\d+ + os_replacement: iOS os_v1_replacement: '11' os_v2_replacement: '2' - - regex: 'CFNetwork/8.{0,100} Darwin/17\.4\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/17\.4\.\d+ + os_replacement: iOS os_v1_replacement: '11' os_v2_replacement: '2' os_v3_replacement: '6' - - regex: 'CFNetwork/8.{0,100} Darwin/17\.5\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/17\.5\.\d+ + os_replacement: iOS os_v1_replacement: '11' os_v2_replacement: '3' - - regex: 'CFNetwork/9.{0,100} Darwin/17\.6\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/9.{0,100} Darwin/17\.6\.\d+ + os_replacement: iOS os_v1_replacement: '11' os_v2_replacement: '4' - - regex: 'CFNetwork/9.{0,100} Darwin/17\.7\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/9.{0,100} Darwin/17\.7\.\d+ + os_replacement: iOS os_v1_replacement: '11' os_v2_replacement: '4' os_v3_replacement: '1' - - regex: 'CFNetwork/8.{0,100} Darwin/(17)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/8.{0,100} Darwin/(17)\.\d+ + os_replacement: iOS os_v1_replacement: '11' - - regex: 'CFNetwork/9.{0,100} Darwin/18\.0\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/9.{0,100} Darwin/18\.0\.\d+ + os_replacement: iOS os_v1_replacement: '12' os_v2_replacement: '0' - - regex: 'CFNetwork/9.{0,100} Darwin/18\.2\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/9.{0,100} Darwin/18\.2\.\d+ + os_replacement: iOS os_v1_replacement: '12' os_v2_replacement: '1' - - regex: 'CFNetwork/9.{0,100} Darwin/18\.5\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/9.{0,100} Darwin/18\.5\.\d+ + os_replacement: iOS os_v1_replacement: '12' os_v2_replacement: '2' - - regex: 'CFNetwork/9.{0,100} Darwin/18\.6\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/9.{0,100} Darwin/18\.6\.\d+ + os_replacement: iOS os_v1_replacement: '12' os_v2_replacement: '3' - - regex: 'CFNetwork/9.{0,100} Darwin/18\.7\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/9.{0,100} Darwin/18\.7\.\d+ + os_replacement: iOS os_v1_replacement: '12' os_v2_replacement: '4' - - regex: 'CFNetwork/9.{0,100} Darwin/(18)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/9.{0,100} Darwin/(18)\.\d+ + os_replacement: iOS os_v1_replacement: '12' - - regex: 'CFNetwork/11.{0,100} Darwin/19\.2\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/11.{0,100} Darwin/19\.2\.\d+ + os_replacement: iOS os_v1_replacement: '13' os_v2_replacement: '3' - - regex: 'CFNetwork/11.{0,100} Darwin/19\.3\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/11.{0,100} Darwin/19\.3\.\d+ + os_replacement: iOS os_v1_replacement: '13' os_v2_replacement: '3' os_v3_replacement: '1' - - regex: 'CFNetwork/11.{0,100} Darwin/19\.4\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/11.{0,100} Darwin/19\.4\.\d+ + os_replacement: iOS os_v1_replacement: '13' os_v2_replacement: '4' - - regex: 'CFNetwork/11.{0,100} Darwin/19\.5\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/11.{0,100} Darwin/19\.5\.\d+ + os_replacement: iOS os_v1_replacement: '13' os_v2_replacement: '5' - - regex: 'CFNetwork/11.{0,100} Darwin/19\.6\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/11.{0,100} Darwin/19\.6\.\d+ + os_replacement: iOS os_v1_replacement: '13' os_v2_replacement: '6' - - regex: 'CFNetwork/1[01].{0,100} Darwin/19\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/1[01].{0,100} Darwin/19\.\d+ + os_replacement: iOS os_v1_replacement: '13' - - regex: 'CFNetwork/12.{0,100} Darwin/20\.1\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/12.{0,100} Darwin/20\.1\.\d+ + os_replacement: iOS os_v1_replacement: '14' os_v2_replacement: '2' - - regex: 'CFNetwork/12.{0,100} Darwin/20\.2\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/12.{0,100} Darwin/20\.2\.\d+ + os_replacement: iOS os_v1_replacement: '14' os_v2_replacement: '3' - - regex: 'CFNetwork/12.{0,100} Darwin/20\.3\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/12.{0,100} Darwin/20\.3\.\d+ + os_replacement: iOS os_v1_replacement: '14' os_v2_replacement: '4' - - regex: 'CFNetwork/12.{0,100} Darwin/20\.4\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/12.{0,100} Darwin/20\.4\.\d+ + os_replacement: iOS os_v1_replacement: '14' os_v2_replacement: '5' - - regex: 'CFNetwork/12.{0,100} Darwin/20\.5\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/12.{0,100} Darwin/20\.5\.\d+ + os_replacement: iOS os_v1_replacement: '14' os_v2_replacement: '6' - - regex: 'CFNetwork/12.{0,100} Darwin/20\.6\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/12.{0,100} Darwin/20\.6\.\d+ + os_replacement: iOS os_v1_replacement: '14' os_v2_replacement: '8' - - regex: 'CFNetwork/.{0,100} Darwin/(20)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/(20)\.\d+ + os_replacement: iOS os_v1_replacement: '14' - - regex: 'CFNetwork/13.{0,100} Darwin/21\.0\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/13.{0,100} Darwin/21\.0\.\d+ + os_replacement: iOS os_v1_replacement: '15' os_v2_replacement: '0' - - regex: 'CFNetwork/13.{0,100} Darwin/21\.1\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/13.{0,100} Darwin/21\.1\.\d+ + os_replacement: iOS os_v1_replacement: '15' os_v2_replacement: '1' - - regex: 'CFNetwork/13.{0,100} Darwin/21\.2\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/13.{0,100} Darwin/21\.2\.\d+ + os_replacement: iOS os_v1_replacement: '15' os_v2_replacement: '2' - - regex: 'CFNetwork/13.{0,100} Darwin/21\.3\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/13.{0,100} Darwin/21\.3\.\d+ + os_replacement: iOS os_v1_replacement: '15' os_v2_replacement: '3' - - regex: 'CFNetwork/13.{0,100} Darwin/21\.4\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/13.{0,100} Darwin/21\.4\.\d+ + os_replacement: iOS os_v1_replacement: '15' os_v2_replacement: '4' - - regex: 'CFNetwork/13.{0,100} Darwin/21\.5\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/13.{0,100} Darwin/21\.5\.\d+ + os_replacement: iOS os_v1_replacement: '15' os_v2_replacement: '5' - - regex: 'CFNetwork/13.{0,100} Darwin/21\.6\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/13.{0,100} Darwin/21\.6\.\d+ + os_replacement: iOS os_v1_replacement: '15' os_v2_replacement: '6' - - regex: 'CFNetwork/.{0,100} Darwin/(21)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/(21)\.\d+ + os_replacement: iOS os_v1_replacement: '15' - - regex: 'CFNetwork/.{0,100} Darwin/22\.0\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/22\.0\.\d+ + os_replacement: iOS os_v1_replacement: '16' os_v2_replacement: '0' - - regex: 'CFNetwork/.{0,100} Darwin/22\.1\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/22\.1\.\d+ + os_replacement: iOS os_v1_replacement: '16' os_v2_replacement: '1' - - regex: 'CFNetwork/.{0,100} Darwin/22\.2\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/22\.2\.\d+ + os_replacement: iOS os_v1_replacement: '16' os_v2_replacement: '2' - - regex: 'CFNetwork/.{0,100} Darwin/22\.3\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/22\.3\.\d+ + os_replacement: iOS os_v1_replacement: '16' os_v2_replacement: '3' - - regex: 'CFNetwork/.{0,100} Darwin/22\.4\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/22\.4\.\d+ + os_replacement: iOS os_v1_replacement: '16' os_v2_replacement: '4' - - regex: 'CFNetwork/.{0,100} Darwin/(22)\.\d+' - os_replacement: 'iOS' + - regex: CFNetwork/.{0,100} Darwin/(22)\.\d+ + os_replacement: iOS os_v1_replacement: '16' - - regex: 'CFNetwork/.{0,100} Darwin/' - os_replacement: 'iOS' - - # iOS Apps + - regex: CFNetwork/.{0,100} Darwin/ + os_replacement: iOS - regex: '\b(iOS[ /]|iOS; |iPhone(?:/| v|[ _]OS[/,]|; | OS : |\d,\d/|\d,\d; )|iPad/)(\d{1,2})[_\.](\d{1,2})(?:[_\.](\d+)|)' - os_replacement: 'iOS' - - regex: '\((iOS);' - - ########## - # Apple Watch - ########## - - regex: '(watchOS)[/ ](\d+)\.(\d+)(?:\.(\d+)|)' - os_replacement: 'WatchOS' - - ########################## - # Outlook on iOS >= 2.62.0 - ########################## - - regex: 'Outlook-(iOS)/\d+\.\d+\.prod\.iphone' - - ########################## - # iOS devices, the same regex matches mobile safari webviews - ########################## - - regex: '(iPod|iPhone|iPad)' - os_replacement: 'iOS' - - ########## - # Apple TV - ########## - - regex: '(tvOS)[/ ](\d+)\.(\d+)(?:\.(\d+)|)' - os_replacement: 'tvOS' - - ########## - # Chrome OS - # if version 0.0.0, probably this stuff: - # http://code.google.com/p/chromium-os/issues/detail?id=11573 - # http://code.google.com/p/chromium-os/issues/detail?id=13790 - ########## - - regex: '(CrOS) [a-z0-9_]+ (\d+)\.(\d+)(?:\.(\d+)|)' - os_replacement: 'Chrome OS' - - ########## - # Linux distros - ########## - - regex: '([Dd]ebian)' - os_replacement: 'Debian' - - regex: '(Linux Mint)(?:/(\d+)|)' + os_replacement: iOS + - regex: \((iOS); + - regex: (watchOS)[/ ](\d+)\.(\d+)(?:\.(\d+)|) + os_replacement: WatchOS + - regex: Outlook-(iOS)/\d+\.\d+\.prod\.iphone + - regex: (iPod|iPhone|iPad) + os_replacement: iOS + - regex: (tvOS)[/ ](\d+)\.(\d+)(?:\.(\d+)|) + os_replacement: tvOS + - regex: (CrOS) [a-z0-9_]+ (\d+)\.(\d+)(?:\.(\d+)|) + os_replacement: Chrome OS + - regex: ([Dd]ebian) + os_replacement: Debian + - regex: (Linux Mint)(?:/(\d+)|) - regex: '(Mandriva)(?: Linux|)/(?:[\d.-]+m[a-z]{2}(\d+).(\d)|)' - - ########## - # Symbian + Symbian OS - # http://en.wikipedia.org/wiki/History_of_Symbian - ########## - - regex: '(Symbian[Oo][Ss])[/ ](\d+)\.(\d+)' - os_replacement: 'Symbian OS' - - regex: '(Symbian/3).{1,200}NokiaBrowser/7\.3' - os_replacement: 'Symbian^3 Anna' - - regex: '(Symbian/3).{1,200}NokiaBrowser/7\.4' - os_replacement: 'Symbian^3 Belle' - - regex: '(Symbian/3)' - os_replacement: 'Symbian^3' - - regex: '\b(Series 60|SymbOS|S60Version|S60V\d|S60\b)' - os_replacement: 'Symbian OS' - - regex: '(MeeGo)' - - regex: 'Symbian [Oo][Ss]' - os_replacement: 'Symbian OS' - - regex: 'Series40;' - os_replacement: 'Nokia Series 40' - - regex: 'Series30Plus;' - os_replacement: 'Nokia Series 30 Plus' - - ########## - # BlackBerry devices - ########## - - regex: '(BB10);.{1,200}Version/(\d+)\.(\d+)\.(\d+)' - os_replacement: 'BlackBerry OS' - - regex: '(Black[Bb]erry)[0-9a-z]+/(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|)' - os_replacement: 'BlackBerry OS' - - regex: '(Black[Bb]erry).{1,200}Version/(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|)' - os_replacement: 'BlackBerry OS' - - regex: '(RIM Tablet OS) (\d+)\.(\d+)\.(\d+)' - os_replacement: 'BlackBerry Tablet OS' - - regex: '(Play[Bb]ook)' - os_replacement: 'BlackBerry Tablet OS' - - regex: '(Black[Bb]erry)' - os_replacement: 'BlackBerry OS' - - ########## - # KaiOS - ########## - - regex: '(K[Aa][Ii]OS)\/(\d+)\.(\d+)(?:\.(\d+)|)' - os_replacement: 'KaiOS' - - ########## - # Firefox OS - ########## - - regex: '\((?:Mobile|Tablet);.{1,200}Gecko/18.0 Firefox/\d+\.\d+' - os_replacement: 'Firefox OS' + - regex: (Symbian[Oo][Ss])[/ ](\d+)\.(\d+) + os_replacement: Symbian OS + - regex: (Symbian/3).{1,200}NokiaBrowser/7\.3 + os_replacement: Symbian^3 Anna + - regex: (Symbian/3).{1,200}NokiaBrowser/7\.4 + os_replacement: Symbian^3 Belle + - regex: (Symbian/3) + os_replacement: Symbian^3 + - regex: \b(Series 60|SymbOS|S60Version|S60V\d|S60\b) + os_replacement: Symbian OS + - regex: (MeeGo) + - regex: Symbian [Oo][Ss] + os_replacement: Symbian OS + - regex: Series40; + os_replacement: Nokia Series 40 + - regex: Series30Plus; + os_replacement: Nokia Series 30 Plus + - regex: (BB10);.{1,200}Version/(\d+)\.(\d+)\.(\d+) + os_replacement: BlackBerry OS + - regex: (Black[Bb]erry)[0-9a-z]+/(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|) + os_replacement: BlackBerry OS + - regex: (Black[Bb]erry).{1,200}Version/(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|) + os_replacement: BlackBerry OS + - regex: (RIM Tablet OS) (\d+)\.(\d+)\.(\d+) + os_replacement: BlackBerry Tablet OS + - regex: (Play[Bb]ook) + os_replacement: BlackBerry Tablet OS + - regex: (Black[Bb]erry) + os_replacement: BlackBerry OS + - regex: (K[Aa][Ii]OS)\/(\d+)\.(\d+)(?:\.(\d+)|) + os_replacement: KaiOS + - regex: \((?:Mobile|Tablet);.{1,200}Gecko/18.0 Firefox/\d+\.\d+ + os_replacement: Firefox OS os_v1_replacement: '1' os_v2_replacement: '0' os_v3_replacement: '1' - - - regex: '\((?:Mobile|Tablet);.{1,200}Gecko/18.1 Firefox/\d+\.\d+' - os_replacement: 'Firefox OS' + - regex: \((?:Mobile|Tablet);.{1,200}Gecko/18.1 Firefox/\d+\.\d+ + os_replacement: Firefox OS os_v1_replacement: '1' os_v2_replacement: '1' - - - regex: '\((?:Mobile|Tablet);.{1,200}Gecko/26.0 Firefox/\d+\.\d+' - os_replacement: 'Firefox OS' + - regex: \((?:Mobile|Tablet);.{1,200}Gecko/26.0 Firefox/\d+\.\d+ + os_replacement: Firefox OS os_v1_replacement: '1' os_v2_replacement: '2' - - - regex: '\((?:Mobile|Tablet);.{1,200}Gecko/28.0 Firefox/\d+\.\d+' - os_replacement: 'Firefox OS' + - regex: \((?:Mobile|Tablet);.{1,200}Gecko/28.0 Firefox/\d+\.\d+ + os_replacement: Firefox OS os_v1_replacement: '1' os_v2_replacement: '3' - - - regex: '\((?:Mobile|Tablet);.{1,200}Gecko/30.0 Firefox/\d+\.\d+' - os_replacement: 'Firefox OS' + - regex: \((?:Mobile|Tablet);.{1,200}Gecko/30.0 Firefox/\d+\.\d+ + os_replacement: Firefox OS os_v1_replacement: '1' os_v2_replacement: '4' - - - regex: '\((?:Mobile|Tablet);.{1,200}Gecko/32.0 Firefox/\d+\.\d+' - os_replacement: 'Firefox OS' + - regex: \((?:Mobile|Tablet);.{1,200}Gecko/32.0 Firefox/\d+\.\d+ + os_replacement: Firefox OS os_v1_replacement: '2' os_v2_replacement: '0' - - - regex: '\((?:Mobile|Tablet);.{1,200}Gecko/34.0 Firefox/\d+\.\d+' - os_replacement: 'Firefox OS' + - regex: \((?:Mobile|Tablet);.{1,200}Gecko/34.0 Firefox/\d+\.\d+ + os_replacement: Firefox OS os_v1_replacement: '2' os_v2_replacement: '1' - - # Firefox OS Generic - - regex: '\((?:Mobile|Tablet);.{1,200}Firefox/\d+\.\d+' - os_replacement: 'Firefox OS' - - - ########## - # BREW - # yes, Brew is lower-cased for Brew MP - ########## - - regex: '(BREW)[ /](\d+)\.(\d+)\.(\d+)' - - regex: '(BREW);' - - regex: '(Brew MP|BMP)[ /](\d+)\.(\d+)\.(\d+)' - os_replacement: 'Brew MP' - - regex: 'BMP;' - os_replacement: 'Brew MP' - - ########## - # Google TV - ########## + - regex: \((?:Mobile|Tablet);.{1,200}Firefox/\d+\.\d+ + os_replacement: Firefox OS + - regex: (BREW)[ /](\d+)\.(\d+)\.(\d+) + - regex: (BREW); + - regex: (Brew MP|BMP)[ /](\d+)\.(\d+)\.(\d+) + os_replacement: Brew MP + - regex: BMP; + os_replacement: Brew MP - regex: '(GoogleTV)(?: (\d+)\.(\d+)(?:\.(\d+)|)|/[\da-z]+)' - - - regex: '(WebTV)/(\d+).(\d+)' - - ########## - # Chromecast - ########## - - regex: '(CrKey)(?:[/](\d+)\.(\d+)(?:\.(\d+)|)|)' - os_replacement: 'Chromecast' - - ########## - # Misc mobile - ########## - - regex: '(hpw|web)OS/(\d+)\.(\d+)(?:\.(\d+)|)' - os_replacement: 'webOS' - - regex: '(VRE);' - - ########## - # Generic patterns - # since the majority of os cases are very specific, these go last - ########## - - regex: '(Fedora|Red Hat|PCLinuxOS|Puppy|Ubuntu|Kindle|Bada|Sailfish|Lubuntu|BackTrack|Slackware|(?:Free|Open|Net|\b)BSD)[/ ](\d+)\.(\d+)(?:\.(\d+)|)(?:\.(\d+)|)' - - # Gentoo Linux + Kernel Version - - regex: '(Linux)[ /](\d+)\.(\d+)(?:\.(\d+)|).{0,100}gentoo' - os_replacement: 'Gentoo' - - # Opera Mini Bada - - regex: '\((Bada);' - - # just os - - regex: '(Windows|Android|WeTab|Maemo|Web0S)' - - regex: '(Ubuntu|Kubuntu|Arch Linux|CentOS|Slackware|Gentoo|openSUSE|SUSE|Red Hat|Fedora|PCLinuxOS|Mageia|SerenityOS|(?:Free|Open|Net|\b)BSD)' - # Linux + Kernel Version - - regex: '(Linux)(?:[ /](\d+)\.(\d+)(?:\.(\d+)|)|)' - - regex: 'SunOS' - os_replacement: 'Solaris' - # Wget/x.x.x (linux-gnu) - - regex: '\(linux-gnu\)' - os_replacement: 'Linux' - - regex: '\(x86_64-redhat-linux-gnu\)' - os_replacement: 'Red Hat' - - regex: '\((freebsd)(\d+)\.(\d+)\)' - os_replacement: 'FreeBSD' - - regex: 'linux' - os_replacement: 'Linux' - - # Roku Digital-Video-Players https://www.roku.com/ - - regex: '^(Roku)/DVP-(\d+)\.(\d+)' - + - regex: (WebTV)/(\d+).(\d+) + - regex: (hpw|web)OS/(\d+)\.(\d+)(?:\.(\d+)|) + os_replacement: webOS + - regex: (VRE); + - regex: (Fedora|Red Hat|PCLinuxOS|Puppy|Ubuntu|Kindle|Bada|Sailfish|Lubuntu|BackTrack|Slackware|(?:Free|Open|Net|\b)BSD)[/ + ](\d+)\.(\d+)(?:\.(\d+)|)(?:\.(\d+)|) + - regex: (Linux)[ /](\d+)\.(\d+)(?:\.(\d+)|).{0,100}gentoo + os_replacement: Gentoo + - regex: \((Bada); + - regex: (Windows|Android|WeTab|Maemo|Web0S) + - regex: (Ubuntu|Kubuntu|Arch Linux|CentOS|Slackware|Gentoo|openSUSE|SUSE|Red Hat|Fedora|PCLinuxOS|Mageia|SerenityOS|(?:Free|Open|Net|\b)BSD) + - regex: (Linux)(?:[ /](\d+)\.(\d+)(?:\.(\d+)|)|) + - regex: SunOS + os_replacement: Solaris + - regex: \(linux-gnu\) + os_replacement: Linux + - regex: \(x86_64-redhat-linux-gnu\) + os_replacement: Red Hat + - regex: \((freebsd)(\d+)\.(\d+)\) + os_replacement: FreeBSD + - regex: linux + os_replacement: Linux + - regex: ^(Roku)/DVP-(\d+)\.(\d+) device_parsers: - - ######### - # Mobile Spiders - # Catch the mobile crawler before checking for iPhones / Androids. - ######### - - regex: '^.{0,100}?(?:(?:iPhone|Windows CE|Windows Phone|Android).{0,300}(?:(?:Bot|Yeti)-Mobile|YRSpider|BingPreview|bots?/\d|(?:bot|spider)\.html)|AdsBot-Google-Mobile.{0,200}iPhone)' - regex_flag: 'i' - device_replacement: 'Spider' - brand_replacement: 'Spider' - model_replacement: 'Smartphone' - - regex: '^.{0,100}?(?:DoCoMo|\bMOT\b|\bLG\b|Nokia|Samsung|SonyEricsson).{0,200}(?:(?:Bot|Yeti)-Mobile|bots?/\d|(?:bot|crawler)\.html|(?:jump|google|Wukong)bot|ichiro/mobile|/spider|YahooSeeker)' - regex_flag: 'i' - device_replacement: 'Spider' - brand_replacement: 'Spider' - model_replacement: 'Feature Phone' - - # PTST / WebPageTest.org crawlers + - regex: ^.{0,100}?(?:(?:iPhone|Windows CE|Windows Phone|Android).{0,300}(?:(?:Bot|Yeti)-Mobile|YRSpider|BingPreview|bots?/\d|(?:bot|spider)\.html)|AdsBot-Google-Mobile.{0,200}iPhone) + regex_flag: i + device_replacement: Spider + brand_replacement: Spider + model_replacement: Smartphone + - regex: ^.{0,100}?(?:DoCoMo|\bMOT\b|\bLG\b|Nokia|Samsung|SonyEricsson).{0,200}(?:(?:Bot|Yeti)-Mobile|bots?/\d|(?:bot|crawler)\.html|(?:jump|google|Wukong)bot|ichiro/mobile|/spider|YahooSeeker) + regex_flag: i + device_replacement: Spider + brand_replacement: Spider + model_replacement: Feature Phone - regex: ' PTST/\d+(?:\.\d+|)$' - device_replacement: 'Spider' - brand_replacement: 'Spider' - - # Datanyze.com spider - - regex: 'X11; Datanyze; Linux' - device_replacement: 'Spider' - brand_replacement: 'Spider' - - # aspiegel.com spider (owned by Huawei) - - regex: 'Mozilla.{1,100}Mobile.{1,100}(AspiegelBot|PetalBot)' - device_replacement: 'Spider' - brand_replacement: 'Spider' - model_replacement: 'Smartphone' - - regex: 'Mozilla.{0,200}(AspiegelBot|PetalBot)' - device_replacement: 'Spider' - brand_replacement: 'Spider' - model_replacement: 'Desktop' - - ######### - # WebBrowser for SmartWatch - # @ref: https://play.google.com/store/apps/details?id=se.vaggan.webbrowser&hl=en - ######### - - regex: '\bSmartWatch {0,2}\( {0,2}([^;]{1,200}) {0,2}; {0,2}([^;]{1,200}) {0,2};' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ###################################################################### - # Android parsers - # - # @ref: https://support.google.com/googleplay/answer/1727131?hl=en - ###################################################################### - - # Android Application - - regex: 'Android Application[^\-]{1,300} - (Sony) ?(Ericsson|) (.{1,200}) \w{1,20} - ' - device_replacement: '$1 $2' - brand_replacement: '$1$2' - model_replacement: '$3' - - regex: 'Android Application[^\-]{1,300} - (?:HTC|HUAWEI|LGE|LENOVO|MEDION|TCT) (HTC|HUAWEI|LG|LENOVO|MEDION|ALCATEL)[ _\-](.{1,200}) \w{1,20} - ' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' + device_replacement: Spider + brand_replacement: Spider + - regex: X11; Datanyze; Linux + device_replacement: Spider + brand_replacement: Spider + - regex: Mozilla.{1,100}Mobile.{1,100}(AspiegelBot|PetalBot) + device_replacement: Spider + brand_replacement: Spider + model_replacement: Smartphone + - regex: Mozilla.{0,200}(AspiegelBot|PetalBot) + device_replacement: Spider + brand_replacement: Spider + model_replacement: Desktop + - regex: \bSmartWatch {0,2}\( {0,2}([^;]{1,200}) {0,2}; {0,2}([^;]{1,200}) {0,2}; + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 + - regex: 'Android Application[^\-]{1,300} - (Sony) ?(Ericsson|) (.{1,200}) \w{1,20} + - ' + device_replacement: $1 $2 + brand_replacement: $1$2 + model_replacement: $3 + - regex: 'Android Application[^\-]{1,300} - (?:HTC|HUAWEI|LGE|LENOVO|MEDION|TCT) + (HTC|HUAWEI|LG|LENOVO|MEDION|ALCATEL)[ _\-](.{1,200}) \w{1,20} - ' + regex_flag: i + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: 'Android Application[^\-]{1,300} - ([^ ]+) (.{1,200}) \w{1,20} - ' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ######### - # 3Q - # @ref: http://www.3q-int.com/ - ######### + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}([BLRQ]C\d{4}[A-Z]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '3Q $1' - brand_replacement: '3Q' - model_replacement: '$1' + device_replacement: 3Q $1 + brand_replacement: 3Q + model_replacement: $1 - regex: '; {0,2}(?:3Q_)([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '3Q $1' - brand_replacement: '3Q' - model_replacement: '$1' - - ######### - # Acer - # @ref: http://us.acer.com/ac/en/US/content/group/tablets - ######### - - regex: 'Android [34].{0,200}; {0,2}(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700(?: Lite| 3G|)|A701|B1-A71|A1-\d{3}|B1-\d{3}|V360|V370|W500|W500P|W501|W501P|W510|W511|W700|Slider SL101|DA22[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Acer' - model_replacement: '$1' + device_replacement: 3Q $1 + brand_replacement: 3Q + model_replacement: $1 + - regex: 'Android [34].{0,200}; {0,2}(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700(?: + Lite| 3G|)|A701|B1-A71|A1-\d{3}|B1-\d{3}|V360|V370|W500|W500P|W501|W501P|W510|W511|W700|Slider + SL101|DA22[^;/]{1,100}?)(?: Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Acer + model_replacement: $1 - regex: '; {0,2}Acer Iconia Tab ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Acer' - model_replacement: '$1' - - regex: '; {0,2}(Z1[1235]0|E320[^/]{0,10}|S500|S510|Liquid[^;/]{0,30}|Iconia A\d+)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Acer' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Acer + model_replacement: $1 + - regex: '; {0,2}(Z1[1235]0|E320[^/]{0,10}|S500|S510|Liquid[^;/]{0,30}|Iconia A\d+)(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Acer + model_replacement: $1 - regex: '; {0,2}(Acer |ACER )([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Acer' - model_replacement: '$2' - - ######### - # Advent - # @ref: https://en.wikipedia.org/wiki/Advent_Vega - # @note: VegaBean and VegaComb (names derived from jellybean, honeycomb) are - # custom ROM builds for Vega - ######### + device_replacement: $1$2 + brand_replacement: Acer + model_replacement: $2 - regex: '; {0,2}(Advent |)(Vega(?:Bean|Comb|)).{0,200}?(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Advent' - model_replacement: '$2' - - ######### - # Ainol - # @ref: http://www.ainol.com/plugin.php?identifier=ainol&module=product - ######### + device_replacement: $1$2 + brand_replacement: Advent + model_replacement: $2 - regex: '; {0,2}(Ainol |)((?:NOVO|[Nn]ovo)[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Ainol' - model_replacement: '$2' - - ######### - # Airis - # @ref: http://airis.es/Tienda/Default.aspx?idG=001 - ######### - - regex: '; {0,2}AIRIS[ _\-]?([^/;\)]+) {0,2}(?:;|\)|Build)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Airis' - model_replacement: '$1' + device_replacement: $1$2 + brand_replacement: Ainol + model_replacement: $2 + - regex: ; {0,2}AIRIS[ _\-]?([^/;\)]+) {0,2}(?:;|\)|Build) + regex_flag: i + device_replacement: $1 + brand_replacement: Airis + model_replacement: $1 - regex: '; {0,2}(OnePAD[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Airis' - model_replacement: '$1' - - ######### - # Airpad - # @ref: ?? - ######### + regex_flag: i + device_replacement: $1 + brand_replacement: Airis + model_replacement: $1 - regex: '; {0,2}Airpad[ \-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Airpad $1' - brand_replacement: 'Airpad' - model_replacement: '$1' - - ######### - # Alcatel - TCT - # @ref: http://www.alcatelonetouch.com/global-en/products/smartphones.html - ######### + device_replacement: Airpad $1 + brand_replacement: Airpad + model_replacement: $1 - regex: '; {0,2}(one ?touch) (EVO7|T10|T20)(?: Build|\) AppleWebKit)' - device_replacement: 'Alcatel One Touch $2' - brand_replacement: 'Alcatel' - model_replacement: 'One Touch $2' - - regex: '; {0,2}(?:alcatel[ _]|)(?:(?:one[ _]?touch[ _])|ot[ \-])([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Alcatel One Touch $1' - brand_replacement: 'Alcatel' - model_replacement: 'One Touch $1' + device_replacement: Alcatel One Touch $2 + brand_replacement: Alcatel + model_replacement: One Touch $2 + - regex: '; {0,2}(?:alcatel[ _]|)(?:(?:one[ _]?touch[ _])|ot[ \-])([^;/]{1,100}?)(?: + Build|\) AppleWebKit)' + regex_flag: i + device_replacement: Alcatel One Touch $1 + brand_replacement: Alcatel + model_replacement: One Touch $1 - regex: '; {0,2}(TCL)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - # operator specific models + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}(Vodafone Smart II|Optimus_Madrid)(?: Build|\) AppleWebKit)' - device_replacement: 'Alcatel $1' - brand_replacement: 'Alcatel' - model_replacement: '$1' + device_replacement: Alcatel $1 + brand_replacement: Alcatel + model_replacement: $1 - regex: '; {0,2}BASE_Lutea_3(?: Build|\) AppleWebKit)' - device_replacement: 'Alcatel One Touch 998' - brand_replacement: 'Alcatel' - model_replacement: 'One Touch 998' + device_replacement: Alcatel One Touch 998 + brand_replacement: Alcatel + model_replacement: One Touch 998 - regex: '; {0,2}BASE_Varia(?: Build|\) AppleWebKit)' - device_replacement: 'Alcatel One Touch 918D' - brand_replacement: 'Alcatel' - model_replacement: 'One Touch 918D' - - ######### - # Allfine - # @ref: http://www.myallfine.com/Products.asp - ######### + device_replacement: Alcatel One Touch 918D + brand_replacement: Alcatel + model_replacement: One Touch 918D - regex: '; {0,2}((?:FINE|Fine)\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Allfine' - model_replacement: '$1' - - ######### - # Allview - # @ref: http://www.allview.ro/produse/droseries/lista-tablete-pc/ - ######### - - regex: '; {0,2}(ALLVIEW[ _]?|Allview[ _]?)((?:Speed|SPEED).{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Allview' - model_replacement: '$2' - - regex: '; {0,2}(ALLVIEW[ _]?|Allview[ _]?|)(AX1_Shine|AX2_Frenzy)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Allview' - model_replacement: '$2' + device_replacement: $1 + brand_replacement: Allfine + model_replacement: $1 + - regex: '; {0,2}(ALLVIEW[ _]?|Allview[ _]?)((?:Speed|SPEED).{0,200}?)(?: Build|\) + AppleWebKit)' + device_replacement: $1$2 + brand_replacement: Allview + model_replacement: $2 + - regex: '; {0,2}(ALLVIEW[ _]?|Allview[ _]?|)(AX1_Shine|AX2_Frenzy)(?: Build|\) + AppleWebKit)' + device_replacement: $1$2 + brand_replacement: Allview + model_replacement: $2 - regex: '; {0,2}(ALLVIEW[ _]?|Allview[ _]?)([^;/]*?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Allview' - model_replacement: '$2' - - ######### - # Allwinner - # @ref: http://www.allwinner.com/ - # @models: A31 (13.3"),A20,A10, - ######### + device_replacement: $1$2 + brand_replacement: Allview + model_replacement: $2 - regex: '; {0,2}(A13-MID)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Allwinner' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Allwinner + model_replacement: $1 - regex: '; {0,2}(Allwinner)[ _\-]?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Allwinner' - model_replacement: '$1' - - ######### - # Amaway - # @ref: http://www.amaway.cn/ - ######### - - regex: '; {0,2}(A651|A701B?|A702|A703|A705|A706|A707|A711|A712|A713|A717|A722|A785|A801|A802|A803|A901|A902|A1002|A1003|A1006|A1007|A9701|A9703|Q710|Q80)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Amaway' - model_replacement: '$1' - - ######### - # Amoi - # @ref: http://www.amoi.com/en/prd/prd_index.jspx - ######### + device_replacement: $1 $2 + brand_replacement: Allwinner + model_replacement: $1 + - regex: '; {0,2}(A651|A701B?|A702|A703|A705|A706|A707|A711|A712|A713|A717|A722|A785|A801|A802|A803|A901|A902|A1002|A1003|A1006|A1007|A9701|A9703|Q710|Q80)(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Amaway + model_replacement: $1 - regex: '; {0,2}(?:AMOI|Amoi)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Amoi $1' - brand_replacement: 'Amoi' - model_replacement: '$1' - - regex: '^(?:AMOI|Amoi)[ _]([^;/]{1,100}?) Linux' - device_replacement: 'Amoi $1' - brand_replacement: 'Amoi' - model_replacement: '$1' - - ######### - # Aoc - # @ref: http://latin.aoc.com/media_tablet - ######### + device_replacement: Amoi $1 + brand_replacement: Amoi + model_replacement: $1 + - regex: ^(?:AMOI|Amoi)[ _]([^;/]{1,100}?) Linux + device_replacement: Amoi $1 + brand_replacement: Amoi + model_replacement: $1 - regex: '; {0,2}(MW(?:0[789]|10)[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Aoc' - model_replacement: '$1' - - ######### - # Aoson - # @ref: http://www.luckystar.com.cn/en/mid.aspx?page=1 - # @ref: http://www.luckystar.com.cn/en/mobiletel.aspx?page=1 - # @note: brand owned by luckystar - ######### - - regex: '; {0,2}(G7|M1013|M1015G|M11[CG]?|M-?12[B]?|M15|M19[G]?|M30[ACQ]?|M31[GQ]|M32|M33[GQ]|M36|M37|M38|M701T|M710|M712B|M713|M715G|M716G|M71(?:G|GS|T|)|M72[T]?|M73[T]?|M75[GT]?|M77G|M79T|M7L|M7LN|M81|M810|M81T|M82|M92|M92KS|M92S|M717G|M721|M722G|M723|M725G|M739|M785|M791|M92SK|M93D)(?: Build|\) AppleWebKit)' - device_replacement: 'Aoson $1' - brand_replacement: 'Aoson' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Aoc + model_replacement: $1 + - regex: '; {0,2}(G7|M1013|M1015G|M11[CG]?|M-?12[B]?|M15|M19[G]?|M30[ACQ]?|M31[GQ]|M32|M33[GQ]|M36|M37|M38|M701T|M710|M712B|M713|M715G|M716G|M71(?:G|GS|T|)|M72[T]?|M73[T]?|M75[GT]?|M77G|M79T|M7L|M7LN|M81|M810|M81T|M82|M92|M92KS|M92S|M717G|M721|M722G|M723|M725G|M739|M785|M791|M92SK|M93D)(?: + Build|\) AppleWebKit)' + device_replacement: Aoson $1 + brand_replacement: Aoson + model_replacement: $1 - regex: '; {0,2}Aoson ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Aoson $1' - brand_replacement: 'Aoson' - model_replacement: '$1' - - ######### - # Apanda - # @ref: http://www.apanda.com.cn/ - ######### + regex_flag: i + device_replacement: Aoson $1 + brand_replacement: Aoson + model_replacement: $1 - regex: '; {0,2}[Aa]panda[ _\-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Apanda $1' - brand_replacement: 'Apanda' - model_replacement: '$1' - - ######### - # Archos - # @ref: http://www.archos.com/de/products/tablets.html - # @ref: http://www.archos.com/de/products/smartphones/index.html - ######### + device_replacement: Apanda $1 + brand_replacement: Apanda + model_replacement: $1 - regex: '; {0,2}(?:ARCHOS|Archos) ?(GAMEPAD.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Archos $1' - brand_replacement: 'Archos' - model_replacement: '$1' - - regex: 'ARCHOS; GOGI; ([^;]{1,200});' - device_replacement: 'Archos $1' - brand_replacement: 'Archos' - model_replacement: '$1' + device_replacement: Archos $1 + brand_replacement: Archos + model_replacement: $1 + - regex: ARCHOS; GOGI; ([^;]{1,200}); + device_replacement: Archos $1 + brand_replacement: Archos + model_replacement: $1 - regex: '(?:ARCHOS|Archos)[ _]?(.{0,200}?)(?: Build|[;/\(\)\-]|$)' - device_replacement: 'Archos $1' - brand_replacement: 'Archos' - model_replacement: '$1' + device_replacement: Archos $1 + brand_replacement: Archos + model_replacement: $1 - regex: '; {0,2}(AN(?:7|8|9|10|13)[A-Z0-9]{1,4})(?: Build|\) AppleWebKit)' - device_replacement: 'Archos $1' - brand_replacement: 'Archos' - model_replacement: '$1' - - regex: '; {0,2}(A28|A32|A43|A70(?:BHT|CHT|HB|S|X)|A101(?:B|C|IT)|A7EB|A7EB-WK|101G9|80G9)(?: Build|\) AppleWebKit)' - device_replacement: 'Archos $1' - brand_replacement: 'Archos' - model_replacement: '$1' - - ######### - # A-rival - # @ref: http://www.a-rival.de/de/ - ######### + device_replacement: Archos $1 + brand_replacement: Archos + model_replacement: $1 + - regex: '; {0,2}(A28|A32|A43|A70(?:BHT|CHT|HB|S|X)|A101(?:B|C|IT)|A7EB|A7EB-WK|101G9|80G9)(?: + Build|\) AppleWebKit)' + device_replacement: Archos $1 + brand_replacement: Archos + model_replacement: $1 - regex: '; {0,2}(PAD-FMD[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Arival' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Arival + model_replacement: $1 - regex: '; {0,2}(BioniQ) ?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Arival' - model_replacement: '$1 $2' - - ######### - # Arnova - # @ref: http://arnovatech.com/ - ######### + device_replacement: $1 $2 + brand_replacement: Arival + model_replacement: $1 $2 - regex: '; {0,2}(AN\d[^;/]{1,100}|ARCHM\d+)(?: Build|\) AppleWebKit)' - device_replacement: 'Arnova $1' - brand_replacement: 'Arnova' - model_replacement: '$1' + device_replacement: Arnova $1 + brand_replacement: Arnova + model_replacement: $1 - regex: '; {0,2}(?:ARNOVA|Arnova) ?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Arnova $1' - brand_replacement: 'Arnova' - model_replacement: '$1' - - ######### - # Assistant - # @ref: http://www.assistant.ua - ######### - - regex: '; {0,2}(?:ASSISTANT |)(AP)-?([1789]\d{2}[A-Z]{0,2}|80104)(?: Build|\) AppleWebKit)' - device_replacement: 'Assistant $1-$2' - brand_replacement: 'Assistant' - model_replacement: '$1-$2' - - ######### - # Asus - # @ref: http://www.asus.com/uk/Tablets_Mobile/ - ######### - - regex: '; {0,2}(ME17\d[^;/]*|ME3\d{2}[^;/]{1,100}|K00[A-Z]|Nexus 10|Nexus 7(?: 2013|)|PadFone[^;/]*|Transformer[^;/]*|TF\d{3}[^;/]*|eeepc)(?: Build|\) AppleWebKit)' - device_replacement: 'Asus $1' - brand_replacement: 'Asus' - model_replacement: '$1' + device_replacement: Arnova $1 + brand_replacement: Arnova + model_replacement: $1 + - regex: '; {0,2}(?:ASSISTANT |)(AP)-?([1789]\d{2}[A-Z]{0,2}|80104)(?: Build|\) + AppleWebKit)' + device_replacement: Assistant $1-$2 + brand_replacement: Assistant + model_replacement: $1-$2 + - regex: '; {0,2}(ME17\d[^;/]*|ME3\d{2}[^;/]{1,100}|K00[A-Z]|Nexus 10|Nexus 7(?: + 2013|)|PadFone[^;/]*|Transformer[^;/]*|TF\d{3}[^;/]*|eeepc)(?: Build|\) AppleWebKit)' + device_replacement: Asus $1 + brand_replacement: Asus + model_replacement: $1 - regex: '; {0,2}ASUS[ _]{0,10}([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Asus $1' - brand_replacement: 'Asus' - model_replacement: '$1' - - ######### - # Garmin-Asus - ######### + device_replacement: Asus $1 + brand_replacement: Asus + model_replacement: $1 - regex: '; {0,2}Garmin-Asus ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Garmin-Asus $1' - brand_replacement: 'Garmin-Asus' - model_replacement: '$1' + device_replacement: Garmin-Asus $1 + brand_replacement: Garmin-Asus + model_replacement: $1 - regex: '; {0,2}(Garminfone)(?: Build|\) AppleWebKit)' - device_replacement: 'Garmin $1' - brand_replacement: 'Garmin-Asus' - model_replacement: '$1' - - ######### - # Attab - # @ref: http://www.theattab.com/ - ######### + device_replacement: Garmin $1 + brand_replacement: Garmin-Asus + model_replacement: $1 - regex: '; (@TAB-[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Attab' - model_replacement: '$1' - - ######### - # Audiosonic - # @ref: ?? - # @note: Take care with Docomo T-01 Toshiba - ######### + device_replacement: $1 + brand_replacement: Attab + model_replacement: $1 - regex: '; {0,2}(T-(?:07|[^0]\d)[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Audiosonic' - model_replacement: '$1' - - ######### - # Axioo - # @ref: http://www.axiooworld.com/ww/index.php - ######### - - regex: '; {0,2}(?:Axioo[ _\-]([^;/]{1,100}?)|(picopad)[ _\-]([^;/]{1,100}?))(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Axioo $1$2 $3' - brand_replacement: 'Axioo' - model_replacement: '$1$2 $3' - - ######### - # Azend - # @ref: http://azendcorp.com/index.php/products/portable-electronics - ######### + device_replacement: $1 + brand_replacement: Audiosonic + model_replacement: $1 + - regex: '; {0,2}(?:Axioo[ _\-]([^;/]{1,100}?)|(picopad)[ _\-]([^;/]{1,100}?))(?: + Build|\) AppleWebKit)' + regex_flag: i + device_replacement: Axioo $1$2 $3 + brand_replacement: Axioo + model_replacement: $1$2 $3 - regex: '; {0,2}(V(?:100|700|800)[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Azend' - model_replacement: '$1' - - ######### - # Bak - # @ref: http://www.bakinternational.com/produtos.php?cat=80 - ######### + device_replacement: $1 + brand_replacement: Azend + model_replacement: $1 - regex: '; {0,2}(IBAK\-[^;/]*)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Bak' - model_replacement: '$1' - - ######### - # Bedove - # @ref: http://www.bedove.com/product.html - # @models: HY6501|HY5001|X12|X21|I5 - ######### + regex_flag: i + device_replacement: $1 + brand_replacement: Bak + model_replacement: $1 - regex: '; {0,2}(HY5001|HY6501|X12|X21|I5)(?: Build|\) AppleWebKit)' - device_replacement: 'Bedove $1' - brand_replacement: 'Bedove' - model_replacement: '$1' - - ######### - # Benss - # @ref: http://www.benss.net/ - ######### + device_replacement: Bedove $1 + brand_replacement: Bedove + model_replacement: $1 - regex: '; {0,2}(JC-[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: 'Benss $1' - brand_replacement: 'Benss' - model_replacement: '$1' - - ######### - # Blackberry - # @ref: http://uk.blackberry.com/ - # @note: Android Apps seams to be used here - ######### + device_replacement: Benss $1 + brand_replacement: Benss + model_replacement: $1 - regex: '; {0,2}(BB) ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Blackberry' - model_replacement: '$2' - - ######### - # Blackbird - # @ref: http://iblackbird.co.kr - ######### + device_replacement: $1 $2 + brand_replacement: Blackberry + model_replacement: $2 - regex: '; {0,2}(BlackBird)[ _](I8.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}(BlackBird)[ _](.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ######### - # Blaupunkt - # @ref: http://www.blaupunkt.com - ######### - # Endeavour + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}([0-9]+BP[EM][^;/]*|Endeavour[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Blaupunkt $1' - brand_replacement: 'Blaupunkt' - model_replacement: '$1' - - ######### - # Blu - # @ref: http://bluproducts.com - ######### + device_replacement: Blaupunkt $1 + brand_replacement: Blaupunkt + model_replacement: $1 - regex: '; {0,2}((?:BLU|Blu)[ _\-])([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Blu' - model_replacement: '$2' - # BMOBILE = operator branded device - - regex: '; {0,2}(?:BMOBILE )?(Blu|BLU|DASH [^;/]{1,100}|VIVO 4\.3|TANK 4\.5)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Blu' - model_replacement: '$1' - - ######### - # Blusens - # @ref: http://www.blusens.com/es/?sg=1&sv=al&roc=1 - ######### - # tablet + device_replacement: $1$2 + brand_replacement: Blu + model_replacement: $2 + - regex: '; {0,2}(?:BMOBILE )?(Blu|BLU|DASH [^;/]{1,100}|VIVO 4\.3|TANK 4\.5)(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Blu + model_replacement: $1 - regex: '; {0,2}(TOUCH\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Blusens' - model_replacement: '$1' - - ######### - # Bmobile - # @ref: http://bmobile.eu.com/?categoria=smartphones-2 - # @note: Might collide with Maxx as AX is used also there. - ######### - # smartphone + device_replacement: $1 + brand_replacement: Blusens + model_replacement: $1 - regex: '; {0,2}(AX5\d+)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Bmobile' - model_replacement: '$1' - - ######### - # bq - # @ref: http://bqreaders.com - ######### + device_replacement: $1 + brand_replacement: Bmobile + model_replacement: $1 - regex: '; {0,2}([Bb]q) ([^;/]{1,100}?);?(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'bq' - model_replacement: '$2' + device_replacement: $1 $2 + brand_replacement: bq + model_replacement: $2 - regex: '; {0,2}(Maxwell [^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'bq' - model_replacement: '$1' - - ######### - # Braun Phototechnik - # @ref: http://www.braun-phototechnik.de/en/products/list/~pcat.250/Tablet-PC.html - ######### + device_replacement: $1 + brand_replacement: bq + model_replacement: $1 - regex: '; {0,2}((?:B-Tab|B-TAB) ?\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Braun' - model_replacement: '$1' - - ######### - # Broncho - # @ref: http://www.broncho.cn/ - ######### + device_replacement: $1 + brand_replacement: Braun + model_replacement: $1 - regex: '; {0,2}(Broncho) ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ######### - # Captiva - # @ref: http://www.captiva-power.de - ######### + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}CAPTIVA ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Captiva $1' - brand_replacement: 'Captiva' - model_replacement: '$1' - - ######### - # Casio - # @ref: http://www.casiogzone.com/ - ######### + device_replacement: Captiva $1 + brand_replacement: Captiva + model_replacement: $1 - regex: '; {0,2}(C771|CAL21|IS11CA)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Casio' - model_replacement: '$1' - - ######### - # Cat - # @ref: http://www.cat-sound.com - ######### + device_replacement: $1 + brand_replacement: Casio + model_replacement: $1 - regex: '; {0,2}(?:Cat|CAT) ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Cat $1' - brand_replacement: 'Cat' - model_replacement: '$1' + device_replacement: Cat $1 + brand_replacement: Cat + model_replacement: $1 - regex: '; {0,2}(?:Cat)(Nova.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Cat $1' - brand_replacement: 'Cat' - model_replacement: '$1' + device_replacement: Cat $1 + brand_replacement: Cat + model_replacement: $1 - regex: '; {0,2}(INM8002KP|ADM8000KP_[AB])(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Cat' - model_replacement: 'Tablet PHOENIX 8.1J0' - - ######### - # Celkon - # @ref: http://www.celkonmobiles.com/?_a=products - # @models: A10, A19Q, A101, A105, A107, A107\+, A112, A118, A119, A119Q, A15, A19, A20, A200, A220, A225, A22 Race, A27, A58, A59, A60, A62, A63, A64, A66, A67, A69, A75, A77, A79, A8\+, A83, A85, A86, A87, A89 Ultima, A9\+, A90, A900, A95, A97i, A98, AR 40, AR 45, AR 50, ML5 - ######### - - regex: '; {0,2}(?:[Cc]elkon[ _\*]|CELKON[ _\*])([^;/\)]+) ?(?:Build|;|\))' - device_replacement: '$1' - brand_replacement: 'Celkon' - model_replacement: '$1' - - regex: 'Build/(?:[Cc]elkon)+_?([^;/_\)]+)' - device_replacement: '$1' - brand_replacement: 'Celkon' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Cat + model_replacement: Tablet PHOENIX 8.1J0 + - regex: ; {0,2}(?:[Cc]elkon[ _\*]|CELKON[ _\*])([^;/\)]+) ?(?:Build|;|\)) + device_replacement: $1 + brand_replacement: Celkon + model_replacement: $1 + - regex: Build/(?:[Cc]elkon)+_?([^;/_\)]+) + device_replacement: $1 + brand_replacement: Celkon + model_replacement: $1 - regex: '; {0,2}(CT)-?(\d+)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Celkon' - model_replacement: '$1$2' - # smartphones - - regex: '; {0,2}(A19|A19Q|A105|A107[^;/\)]*) ?(?:Build|;|\))' - device_replacement: '$1' - brand_replacement: 'Celkon' - model_replacement: '$1' - - ######### - # ChangJia - # @ref: http://www.cjshowroom.com/eproducts.aspx?classcode=004001001 - # @brief: China manufacturer makes tablets for different small brands - # (eg. http://www.zeepad.net/index.html) - ######### + device_replacement: $1$2 + brand_replacement: Celkon + model_replacement: $1$2 + - regex: ; {0,2}(A19|A19Q|A105|A107[^;/\)]*) ?(?:Build|;|\)) + device_replacement: $1 + brand_replacement: Celkon + model_replacement: $1 - regex: '; {0,2}(TPC[0-9]{4,5})(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'ChangJia' - model_replacement: '$1' - - ######### - # Cloudfone - # @ref: http://www.cloudfonemobile.com/ - ######### + device_replacement: $1 + brand_replacement: ChangJia + model_replacement: $1 + - regex: CrKey.*DeviceType/([^/]*) + brand_replacement: Google + device_replacement: Chromecast + model_replacement: $1 + - regex: Fuchsia.*CrKey + brand_replacement: Google + device_replacement: Chromecast + model_replacement: Nest Hub + - regex: Linux.*CrKey/1.36 + brand_replacement: Google + device_replacement: Chromecast + model_replacement: First Generation + - regex: CrKey/ + brand_replacement: Google + device_replacement: Chromecast + model_replacement: Chromecast - regex: '; {0,2}(Cloudfone)[ _](Excite)([^ ][^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2 $3' - brand_replacement: 'Cloudfone' - model_replacement: '$1 $2 $3' + device_replacement: $1 $2 $3 + brand_replacement: Cloudfone + model_replacement: $1 $2 $3 - regex: '; {0,2}(Excite|ICE)[ _](\d+[^;/]{0,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Cloudfone $1 $2' - brand_replacement: 'Cloudfone' - model_replacement: 'Cloudfone $1 $2' + device_replacement: Cloudfone $1 $2 + brand_replacement: Cloudfone + model_replacement: Cloudfone $1 $2 - regex: '; {0,2}(Cloudfone|CloudPad)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Cloudfone' - model_replacement: '$1 $2' - - ######### - # Cmx - # @ref: http://cmx.at/de/ - ######### + device_replacement: $1 $2 + brand_replacement: Cloudfone + model_replacement: $1 $2 - regex: '; {0,2}((?:Aquila|Clanga|Rapax)[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Cmx' - model_replacement: '$1' - - ######### - # CobyKyros - # @ref: http://cobykyros.com - # @note: Be careful with MID\d{3} from MpMan or Manta - ######### - - regex: '; {0,2}(?:CFW-|Kyros )?(MID[0-9]{4}(?:[ABC]|SR|TV)?)(\(3G\)-4G| GB 8K| 3G| 8K| GB)? {0,2}(?:Build|[;\)])' - device_replacement: 'CobyKyros $1$2' - brand_replacement: 'CobyKyros' - model_replacement: '$1$2' - - ######### - # Coolpad - # @ref: ?? - ######### + regex_flag: i + device_replacement: $1 + brand_replacement: Cmx + model_replacement: $1 + - regex: ; {0,2}(?:CFW-|Kyros )?(MID[0-9]{4}(?:[ABC]|SR|TV)?)(\(3G\)-4G| GB 8K| + 3G| 8K| GB)? {0,2}(?:Build|[;\)]) + device_replacement: CobyKyros $1$2 + brand_replacement: CobyKyros + model_replacement: $1$2 - regex: '; {0,2}([^;/]{0,50})Coolpad[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Coolpad' - model_replacement: '$1$2' - - ######### - # Cube - # @ref: http://www.cube-tablet.com/buy-products.html - ######### + device_replacement: $1$2 + brand_replacement: Coolpad + model_replacement: $1$2 - regex: '; {0,2}(CUBE[ _])?([KU][0-9]+ ?GT.{0,200}?|A5300)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1$2' - brand_replacement: 'Cube' - model_replacement: '$2' - - ######### - # Cubot - # @ref: http://www.cubotmall.com/ - ######### + regex_flag: i + device_replacement: $1$2 + brand_replacement: Cube + model_replacement: $2 - regex: '; {0,2}CUBOT ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Cubot' - model_replacement: '$1' + regex_flag: i + device_replacement: $1 + brand_replacement: Cubot + model_replacement: $1 - regex: '; {0,2}(BOBBY)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Cubot' - model_replacement: '$1' - - ######### - # Danew - # @ref: http://www.danew.com/produits-tablette.php - ######### + regex_flag: i + device_replacement: $1 + brand_replacement: Cubot + model_replacement: $1 - regex: '; {0,2}(Dslide [^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Danew' - model_replacement: '$1' - - ######### - # Dell - # @ref: http://www.dell.com - # @ref: http://www.softbank.jp/mobile/support/product/101dl/ - # @ref: http://www.softbank.jp/mobile/support/product/001dl/ - # @ref: http://developer.emnet.ne.jp/android.html - # @ref: http://www.dell.com/in/p/mobile-xcd28/pd - # @ref: http://www.dell.com/in/p/mobile-xcd35/pd - ######### + device_replacement: $1 + brand_replacement: Danew + model_replacement: $1 - regex: '; {0,2}(XCD)[ _]?(28|35)(?: Build|\) AppleWebKit)' - device_replacement: 'Dell $1$2' - brand_replacement: 'Dell' - model_replacement: '$1$2' + device_replacement: Dell $1$2 + brand_replacement: Dell + model_replacement: $1$2 - regex: '; {0,2}(001DL)(?: Build|\) AppleWebKit)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: 'Streak' + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: Streak - regex: '; {0,2}(?:Dell|DELL) (Streak)(?: Build|\) AppleWebKit)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: 'Streak' + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: Streak - regex: '; {0,2}(101DL|GS01|Streak Pro[^;/]{0,100})(?: Build|\) AppleWebKit)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: 'Streak Pro' + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: Streak Pro - regex: '; {0,2}([Ss]treak ?7)(?: Build|\) AppleWebKit)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: 'Streak 7' + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: Streak 7 - regex: '; {0,2}(Mini-3iX)(?: Build|\) AppleWebKit)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: '$1' - - regex: '; {0,2}(?:Dell|DELL)[ _](Aero|Venue|Thunder|Mini.{0,200}?|Streak[ _]Pro)(?: Build|\) AppleWebKit)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: '$1' + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: $1 + - regex: '; {0,2}(?:Dell|DELL)[ _](Aero|Venue|Thunder|Mini.{0,200}?|Streak[ _]Pro)(?: + Build|\) AppleWebKit)' + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: $1 - regex: '; {0,2}Dell[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: '$1' - - ######### - # Denver - # @ref: http://www.denver-electronics.com/tablets1/ - ######### + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: $1 - regex: '; {0,2}(TA[CD]-\d+[^;/]{0,100})(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Denver' - model_replacement: '$1' - - ######### - # Dex - # @ref: http://dex.ua/ - ######### + device_replacement: $1 + brand_replacement: Denver + model_replacement: $1 - regex: '; {0,2}(iP[789]\d{2}(?:-3G)?|IP10\d{2}(?:-8GB)?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Dex' - model_replacement: '$1' - - ######### - # DNS AirTab - # @ref: http://www.dns-shop.ru/ - ######### + device_replacement: $1 + brand_replacement: Dex + model_replacement: $1 - regex: '; {0,2}(AirTab)[ _\-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'DNS' - model_replacement: '$1 $2' - - ######### - # Docomo (Operator Branded Device) - # @ref: http://www.ipentec.com/document/document.aspx?page=android-useragent - ######### + device_replacement: $1 $2 + brand_replacement: DNS + model_replacement: $1 $2 - regex: '; {0,2}(F\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Fujitsu' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Fujitsu + model_replacement: $1 - regex: '; {0,2}(HT-03A)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: 'Magic' + device_replacement: $1 + brand_replacement: HTC + model_replacement: Magic - regex: '; {0,2}(HT\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: HTC + model_replacement: $1 - regex: '; {0,2}(L\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'LG' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: LG + model_replacement: $1 - regex: '; {0,2}(N\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Nec' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Nec + model_replacement: $1 - regex: '; {0,2}(P\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Panasonic' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Panasonic + model_replacement: $1 - regex: '; {0,2}(SC\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Samsung' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Samsung + model_replacement: $1 - regex: '; {0,2}(SH\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Sharp' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Sharp + model_replacement: $1 - regex: '; {0,2}(SO\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'SonyEricsson' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: SonyEricsson + model_replacement: $1 - regex: '; {0,2}(T\-0[12][^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Toshiba' - model_replacement: '$1' - - ######### - # DOOV - # @ref: http://www.doov.com.cn/ - ######### + device_replacement: $1 + brand_replacement: Toshiba + model_replacement: $1 - regex: '; {0,2}(DOOV)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'DOOV' - model_replacement: '$2' - - ######### - # Enot - # @ref: http://www.enot.ua/ - ######### + device_replacement: $1 $2 + brand_replacement: DOOV + model_replacement: $2 - regex: '; {0,2}(Enot|ENOT)[ -]?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Enot' - model_replacement: '$2' - - ######### - # Evercoss - # @ref: http://evercoss.com/android/ - ######### - - regex: '; {0,2}[^;/]{1,100} Build/(?:CROSS|Cross)+[ _\-]([^\)]+)' - device_replacement: 'CROSS $1' - brand_replacement: 'Evercoss' - model_replacement: 'Cross $1' + device_replacement: $1 $2 + brand_replacement: Enot + model_replacement: $2 + - regex: ; {0,2}[^;/]{1,100} Build/(?:CROSS|Cross)+[ _\-]([^\)]+) + device_replacement: CROSS $1 + brand_replacement: Evercoss + model_replacement: Cross $1 - regex: '; {0,2}(CROSS|Cross)[ _\-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Evercoss' - model_replacement: 'Cross $2' - - ######### - # Explay - # @ref: http://explay.ru/ - ######### - - regex: '; {0,2}Explay[_ ](.{1,200}?)(?:[\)]| Build)' - device_replacement: '$1' - brand_replacement: 'Explay' - model_replacement: '$1' - - ######### - # Fly - # @ref: http://www.fly-phone.com/ - ######### + device_replacement: $1 $2 + brand_replacement: Evercoss + model_replacement: Cross $2 + - regex: ; {0,2}Explay[_ ](.{1,200}?)(?:[\)]| Build) + device_replacement: $1 + brand_replacement: Explay + model_replacement: $1 - regex: '; {0,2}(IQ.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Fly' - model_replacement: '$1' - - regex: '; {0,2}(Fly|FLY)[ _](IQ[^;]{1,100}?|F[34]\d+[^;]{0,100}?);?(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Fly' - model_replacement: '$2' - - ######### - # Fujitsu - # @ref: http://www.fujitsu.com/global/ - ######### + device_replacement: $1 + brand_replacement: Fly + model_replacement: $1 + - regex: '; {0,2}(Fly|FLY)[ _](IQ[^;]{1,100}?|F[34]\d+[^;]{0,100}?);?(?: Build|\) + AppleWebKit)' + device_replacement: $1 $2 + brand_replacement: Fly + model_replacement: $2 - regex: '; {0,2}(M532|Q572|FJL21)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Fujitsu' - model_replacement: '$1' - - ######### - # Galapad - # @ref: http://www.galapad.net/product.html - ######### + device_replacement: $1 + brand_replacement: Fujitsu + model_replacement: $1 - regex: '; {0,2}(G1)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Galapad' - model_replacement: '$1' - - ######### - # Geeksphone - # @ref: http://www.geeksphone.com/ - ######### + device_replacement: $1 + brand_replacement: Galapad + model_replacement: $1 - regex: '; {0,2}(Geeksphone) ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ######### - # Gfive - # @ref: http://www.gfivemobile.com/en - ######### + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}(G[^F]?FIVE) ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Gfive' - model_replacement: '$2' - - ######### - # Gionee - # @ref: http://www.gionee.com/ - ######### + device_replacement: $1 $2 + brand_replacement: Gfive + model_replacement: $2 - regex: '; {0,2}(Gionee)[ _\-]([^;/]{1,100}?)(?:/[^;/]{1,100}|)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'Gionee' - model_replacement: '$2' + regex_flag: i + device_replacement: $1 $2 + brand_replacement: Gionee + model_replacement: $2 - regex: '; {0,2}(GN\d+[A-Z]?|INFINITY_PASSION|Ctrl_V1)(?: Build|\) AppleWebKit)' - device_replacement: 'Gionee $1' - brand_replacement: 'Gionee' - model_replacement: '$1' - - regex: '; {0,2}(E3) Build/JOP40D' - device_replacement: 'Gionee $1' - brand_replacement: 'Gionee' - model_replacement: '$1' - - regex: '\sGIONEE[-\s_](\w*)' - regex_flag: 'i' - device_replacement: 'Gionee $1' - brand_replacement: 'Gionee' - model_replacement: '$1' - - ######### - # GoClever - # @ref: http://www.goclever.com - ######### - - regex: '; {0,2}((?:FONE|QUANTUM|INSIGNIA) \d+[^;/]{0,100}|PLAYTAB)(?: Build|\) AppleWebKit)' - device_replacement: 'GoClever $1' - brand_replacement: 'GoClever' - model_replacement: '$1' + device_replacement: Gionee $1 + brand_replacement: Gionee + model_replacement: $1 + - regex: ; {0,2}(E3) Build/JOP40D + device_replacement: Gionee $1 + brand_replacement: Gionee + model_replacement: $1 + - regex: \sGIONEE[-\s_](\w*) + regex_flag: i + device_replacement: Gionee $1 + brand_replacement: Gionee + model_replacement: $1 + - regex: '; {0,2}((?:FONE|QUANTUM|INSIGNIA) \d+[^;/]{0,100}|PLAYTAB)(?: Build|\) + AppleWebKit)' + device_replacement: GoClever $1 + brand_replacement: GoClever + model_replacement: $1 - regex: '; {0,2}GOCLEVER ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'GoClever $1' - brand_replacement: 'GoClever' - model_replacement: '$1' - - ######### - # Google - # @ref: http://www.google.de/glass/start/ - ######### + device_replacement: GoClever $1 + brand_replacement: GoClever + model_replacement: $1 - regex: '; {0,2}(Glass \d+)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Google' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Google + model_replacement: $1 - regex: '; {0,2}([g|G]oogle)? (Pixel[ a-zA-z0-9]{1,100});(?: Build|.{0,50}\) AppleWebKit)' - device_replacement: '$2' - brand_replacement: 'Google' - model_replacement: '$2' + device_replacement: $2 + brand_replacement: Google + model_replacement: $2 - regex: '; {0,2}([g|G]oogle)? (Pixel.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$2' - brand_replacement: 'Google' - model_replacement: '$2' - - ######### - # Gigabyte - # @ref: http://gsmart.gigabytecm.com/en/ - ######### + device_replacement: $2 + brand_replacement: Google + model_replacement: $2 - regex: '; {0,2}(GSmart)[ -]([^/]{1,50})(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Gigabyte' - model_replacement: '$1 $2' - - ######### - # Freescale development boards - # @ref: http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=IMX53QSB - ######### + device_replacement: $1 $2 + brand_replacement: Gigabyte + model_replacement: $1 $2 - regex: '; {0,2}(imx5[13]_[^/]{1,50})(?: Build|\) AppleWebKit)' - device_replacement: 'Freescale $1' - brand_replacement: 'Freescale' - model_replacement: '$1' - - ######### - # Haier - # @ref: http://www.haier.com/ - # @ref: http://www.haier.com/de/produkte/tablet/ - ######### + device_replacement: Freescale $1 + brand_replacement: Freescale + model_replacement: $1 - regex: '; {0,2}Haier[ _\-]([^/]{1,50})(?: Build|\) AppleWebKit)' - device_replacement: 'Haier $1' - brand_replacement: 'Haier' - model_replacement: '$1' + device_replacement: Haier $1 + brand_replacement: Haier + model_replacement: $1 - regex: '; {0,2}(PAD1016)(?: Build|\) AppleWebKit)' - device_replacement: 'Haipad $1' - brand_replacement: 'Haipad' - model_replacement: '$1' - - ######### - # Haipad - # @ref: http://www.haipad.net/ - # @models: V7P|M7SM7S|M9XM9X|M7XM7X|M9|M8|M7-M|M1002|M7|M701 - ######### + device_replacement: Haipad $1 + brand_replacement: Haipad + model_replacement: $1 - regex: '; {0,2}(M701|M7|M8|M9)(?: Build|\) AppleWebKit)' - device_replacement: 'Haipad $1' - brand_replacement: 'Haipad' - model_replacement: '$1' - - ######### - # Hannspree - # @ref: http://www.hannspree.eu/ - # @models: SN10T1|SN10T2|SN70T31B|SN70T32W - ######### + device_replacement: Haipad $1 + brand_replacement: Haipad + model_replacement: $1 - regex: '; {0,2}(SN\d+T[^;\)/]*)(?: Build|[;\)])' - device_replacement: 'Hannspree $1' - brand_replacement: 'Hannspree' - model_replacement: '$1' - - ######### - # HCLme - # @ref: http://www.hclmetablet.com/india/ - ######### - - regex: 'Build/HCL ME Tablet ([^;\)]{1,3})[\);]' - device_replacement: 'HCLme $1' - brand_replacement: 'HCLme' - model_replacement: '$1' - - regex: '; {0,2}([^;\/]+) Build/HCL' - device_replacement: 'HCLme $1' - brand_replacement: 'HCLme' - model_replacement: '$1' - - ######### - # Hena - # @ref: http://www.henadigital.com/en/product/index.asp?id=6 - ######### + device_replacement: Hannspree $1 + brand_replacement: Hannspree + model_replacement: $1 + - regex: Build/HCL ME Tablet ([^;\)]{1,3})[\);] + device_replacement: HCLme $1 + brand_replacement: HCLme + model_replacement: $1 + - regex: ; {0,2}([^;\/]+) Build/HCL + device_replacement: HCLme $1 + brand_replacement: HCLme + model_replacement: $1 - regex: '; {0,2}(MID-?\d{4}C[EM])(?: Build|\) AppleWebKit)' - device_replacement: 'Hena $1' - brand_replacement: 'Hena' - model_replacement: '$1' - - ######### - # Hisense - # @ref: http://www.hisense.com/ - ######### + device_replacement: Hena $1 + brand_replacement: Hena + model_replacement: $1 - regex: '; {0,2}(EG\d{2,}|HS-[^;/]{1,100}|MIRA[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Hisense $1' - brand_replacement: 'Hisense' - model_replacement: '$1' + device_replacement: Hisense $1 + brand_replacement: Hisense + model_replacement: $1 - regex: '; {0,2}(andromax[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Hisense $1' - brand_replacement: 'Hisense' - model_replacement: '$1' - - ######### - # hitech - # @ref: http://www.hitech-mobiles.com/ - ######### + regex_flag: i + device_replacement: Hisense $1 + brand_replacement: Hisense + model_replacement: $1 - regex: '; {0,2}(?:AMAZE[ _](S\d+)|(S\d+)[ _]AMAZE)(?: Build|\) AppleWebKit)' - device_replacement: 'AMAZE $1$2' - brand_replacement: 'hitech' - model_replacement: 'AMAZE $1$2' - - ######### - # HP - # @ref: http://www.hp.com/ - ######### + device_replacement: AMAZE $1$2 + brand_replacement: hitech + model_replacement: AMAZE $1$2 - regex: '; {0,2}(PlayBook)(?: Build|\) AppleWebKit)' - device_replacement: 'HP $1' - brand_replacement: 'HP' - model_replacement: '$1' + device_replacement: HP $1 + brand_replacement: HP + model_replacement: $1 - regex: '; {0,2}HP ([^/]{1,50})(?: Build|\) AppleWebKit)' - device_replacement: 'HP $1' - brand_replacement: 'HP' - model_replacement: '$1' + device_replacement: HP $1 + brand_replacement: HP + model_replacement: $1 - regex: '; {0,2}([^/]{1,30}_tenderloin)(?: Build|\) AppleWebKit)' - device_replacement: 'HP TouchPad' - brand_replacement: 'HP' - model_replacement: 'TouchPad' - - ######### - # Huawei - # @ref: http://www.huaweidevice.com - # @note: Needs to be before HTC due to Desire HD Build on U8815 - ######### - - regex: '; {0,2}(HUAWEI |Huawei-|)([UY][^;/]{1,100}) Build/(?:Huawei|HUAWEI)([UY][^\);]+)\)' - device_replacement: '$1$2' - brand_replacement: 'Huawei' - model_replacement: '$2' - - regex: '; {0,2}([^;/]{1,100}) Build[/ ]Huawei(MT1-U06|[A-Z]{1,50}\d+[^\);]{1,50})\)' - device_replacement: '$1' - brand_replacement: 'Huawei' - model_replacement: '$2' - - regex: '; {0,2}(S7|M860) Build' - device_replacement: '$1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - regex: '; {0,2}((?:HUAWEI|Huawei)[ \-]?)(MediaPad) Build' - device_replacement: '$1$2' - brand_replacement: 'Huawei' - model_replacement: '$2' - - regex: '; {0,2}((?:HUAWEI[ _]?|Huawei[ _]|)Ascend[ _])([^;/]{1,100}) Build' - device_replacement: '$1$2' - brand_replacement: 'Huawei' - model_replacement: '$2' - - regex: '; {0,2}((?:HUAWEI|Huawei)[ _\-]?)((?:G700-|MT-)[^;/]{1,100}) Build' - device_replacement: '$1$2' - brand_replacement: 'Huawei' - model_replacement: '$2' - - regex: '; {0,2}((?:HUAWEI|Huawei)[ _\-]?)([^;/]{1,100}) Build' - device_replacement: '$1$2' - brand_replacement: 'Huawei' - model_replacement: '$2' - - regex: '; {0,2}(MediaPad[^;]{1,200}|SpringBoard) Build/Huawei' - device_replacement: '$1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - regex: '; {0,2}([^;]{1,200}) Build/(?:Huawei|HUAWEI)' - device_replacement: '$1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - regex: '; {0,2}([Uu])([89]\d{3}) Build' - device_replacement: '$1$2' - brand_replacement: 'Huawei' - model_replacement: 'U$2' - - regex: '; {0,2}(?:Ideos |IDEOS )(S7) Build' - device_replacement: 'Huawei Ideos$1' - brand_replacement: 'Huawei' - model_replacement: 'Ideos$1' - - regex: '; {0,2}(?:Ideos |IDEOS )([^;/]{1,50}\s{0,5}|\s{0,5})Build' - device_replacement: 'Huawei Ideos$1' - brand_replacement: 'Huawei' - model_replacement: 'Ideos$1' - - regex: '; {0,2}(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660|Nexus 6P|ATH-.{1,200}?) Build[/ ]' - device_replacement: 'Huawei $1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - regex: '; {0,2}((?:[A-Z]{3})\-L[A-Za0-9]{2})[\)]' - device_replacement: 'Huawei $1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - regex: '; {0,2}([^;]{1,200}) Build/(HONOR|Honor)' - device_replacement: 'Huawei Honor $1' - brand_replacement: 'Huawei' - model_replacement: 'Honor $1' - - ######### - # HTC - # @ref: http://www.htc.com/www/products/ - # @ref: http://en.wikipedia.org/wiki/List_of_HTC_phones - ######### - - - regex: '; {0,2}HTC[ _]([^;]{1,200}); Windows Phone' - device_replacement: 'HTC $1' - brand_replacement: 'HTC' - model_replacement: '$1' - - # Android HTC with Version Number matcher - # ; HTC_0P3Z11/1.12.161.3 Build - # ;HTC_A3335 V2.38.841.1 Build + device_replacement: HP TouchPad + brand_replacement: HP + model_replacement: TouchPad + - regex: ; {0,2}(HUAWEI |Huawei-|)([UY][^;/]{1,100}) Build/(?:Huawei|HUAWEI)([UY][^\);]+)\) + device_replacement: $1$2 + brand_replacement: Huawei + model_replacement: $2 + - regex: ; {0,2}([^;/]{1,100}) Build[/ ]Huawei(MT1-U06|[A-Z]{1,50}\d+[^\);]{1,50})\) + device_replacement: $1 + brand_replacement: Huawei + model_replacement: $2 + - regex: ; {0,2}(S7|M860) Build + device_replacement: $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: ; {0,2}((?:HUAWEI|Huawei)[ \-]?)(MediaPad) Build + device_replacement: $1$2 + brand_replacement: Huawei + model_replacement: $2 + - regex: ; {0,2}((?:HUAWEI[ _]?|Huawei[ _]|)Ascend[ _])([^;/]{1,100}) Build + device_replacement: $1$2 + brand_replacement: Huawei + model_replacement: $2 + - regex: ; {0,2}((?:HUAWEI|Huawei)[ _\-]?)((?:G700-|MT-)[^;/]{1,100}) Build + device_replacement: $1$2 + brand_replacement: Huawei + model_replacement: $2 + - regex: ; {0,2}((?:HUAWEI|Huawei)[ _\-]?)([^;/]{1,100}) Build + device_replacement: $1$2 + brand_replacement: Huawei + model_replacement: $2 + - regex: ; {0,2}(MediaPad[^;]{1,200}|SpringBoard) Build/Huawei + device_replacement: $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: ; {0,2}([^;]{1,200}) Build/(?:Huawei|HUAWEI) + device_replacement: $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: ; {0,2}([Uu])([89]\d{3}) Build + device_replacement: $1$2 + brand_replacement: Huawei + model_replacement: U$2 + - regex: ; {0,2}(?:Ideos |IDEOS )(S7) Build + device_replacement: Huawei Ideos$1 + brand_replacement: Huawei + model_replacement: Ideos$1 + - regex: ; {0,2}(?:Ideos |IDEOS )([^;/]{1,50}\s{0,5}|\s{0,5})Build + device_replacement: Huawei Ideos$1 + brand_replacement: Huawei + model_replacement: Ideos$1 + - regex: ; {0,2}(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660|Nexus + 6P|ATH-.{1,200}?) Build[/ ] + device_replacement: Huawei $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: ; {0,2}((?:[A-Z]{3})\-L[A-Za0-9]{2})[\)] + device_replacement: Huawei $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: ; {0,2}([^;]{1,200}) Build/(HONOR|Honor) + device_replacement: Huawei Honor $1 + brand_replacement: Huawei + model_replacement: Honor $1 + - regex: ; {0,2}HTC[ _]([^;]{1,200}); Windows Phone + device_replacement: HTC $1 + brand_replacement: HTC + model_replacement: $1 - regex: '; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: {0,2}Build|\))' - device_replacement: 'HTC $1' - brand_replacement: 'HTC' - model_replacement: '$1' - - regex: '; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)|)(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: {0,2}Build|\))' - device_replacement: 'HTC $1 $2' - brand_replacement: 'HTC' - model_replacement: '$1 $2' - - regex: '; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: {0,2}Build|\))' - device_replacement: 'HTC $1 $2 $3' - brand_replacement: 'HTC' - model_replacement: '$1 $2 $3' - - regex: '; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)|)(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: {0,2}Build|\))' - device_replacement: 'HTC $1 $2 $3 $4' - brand_replacement: 'HTC' - model_replacement: '$1 $2 $3 $4' - - # Android HTC without Version Number matcher - - regex: '; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/;]+)(?: {0,2}Build|[;\)]| - )' - device_replacement: 'HTC $1' - brand_replacement: 'HTC' - model_replacement: '$1' - - regex: '; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/;\)]+)|)(?: {0,2}Build|[;\)]| - )' - device_replacement: 'HTC $1 $2' - brand_replacement: 'HTC' - model_replacement: '$1 $2' - - regex: '; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/;\)]+)|)|)(?: {0,2}Build|[;\)]| - )' - device_replacement: 'HTC $1 $2 $3' - brand_replacement: 'HTC' - model_replacement: '$1 $2 $3' - - regex: '; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ /;]+)|)|)|)(?: {0,2}Build|[;\)]| - )' - device_replacement: 'HTC $1 $2 $3 $4' - brand_replacement: 'HTC' - model_replacement: '$1 $2 $3 $4' - - # HTC Streaming Player - - regex: 'HTC Streaming Player [^\/]{0,30}/[^\/]{0,10}/ htc_([^/]{1,10}) /' - device_replacement: 'HTC $1' - brand_replacement: 'HTC' - model_replacement: '$1' - # general matcher for anything else - - regex: '(?:[;,] {0,2}|^)(?:htccn_chs-|)HTC[ _-]?([^;]{1,200}?)(?: {0,2}Build|clay|Android|-?Mozilla| Opera| Profile| UNTRUSTED|[;/\(\)]|$)' - regex_flag: 'i' - device_replacement: 'HTC $1' - brand_replacement: 'HTC' - model_replacement: '$1' - # Android matchers without HTC - - regex: '; {0,2}(A6277|ADR6200|ADR6300|ADR6350|ADR6400[A-Z]*|ADR6425[A-Z]*|APX515CKT|ARIA|Desire[^_ ]*|Dream|EndeavorU|Eris|Evo|Flyer|HD2|Hero|HERO200|Hero CDMA|HTL21|Incredible|Inspire[A-Z0-9]*|Legend|Liberty|Nexus ?(?:One|HD2)|One|One S C2|One[ _]?(?:S|V|X\+?)\w*|PC36100|PG06100|PG86100|S31HT|Sensation|Wildfire)(?: Build|[/;\(\)])' - regex_flag: 'i' - device_replacement: 'HTC $1' - brand_replacement: 'HTC' - model_replacement: '$1' - - regex: '; {0,2}(ADR6200|ADR6400L|ADR6425LVW|Amaze|DesireS?|EndeavorU|Eris|EVO|Evo\d[A-Z]+|HD2|IncredibleS?|Inspire[A-Z0-9]*|Inspire[A-Z0-9]*|Sensation[A-Z0-9]*|Wildfire)[ _-](.{1,200}?)(?:[/;\)]|Build|MIUI|1\.0)' - regex_flag: 'i' - device_replacement: 'HTC $1 $2' - brand_replacement: 'HTC' - model_replacement: '$1 $2' - - ######### - # Hyundai - # @ref: http://www.hyundaitechnologies.com - ######### + device_replacement: HTC $1 + brand_replacement: HTC + model_replacement: $1 + - regex: '; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)|)(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: + {0,2}Build|\))' + device_replacement: HTC $1 $2 + brand_replacement: HTC + model_replacement: $1 $2 + - regex: '; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)|)|)(?:[/\\]1\.0 + | V|/| +)\d+\.\d[\d\.]*(?: {0,2}Build|\))' + device_replacement: HTC $1 $2 $3 + brand_replacement: HTC + model_replacement: $1 $2 $3 + - regex: '; {0,2}(?:HTC[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ _/]+)(?:[ _/]([^ + _/]+)|)|)|)(?:[/\\]1\.0 | V|/| +)\d+\.\d[\d\.]*(?: {0,2}Build|\))' + device_replacement: HTC $1 $2 $3 $4 + brand_replacement: HTC + model_replacement: $1 $2 $3 $4 + - regex: '; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/;]+)(?: {0,2}Build|[;\)]| + - )' + device_replacement: HTC $1 + brand_replacement: HTC + model_replacement: $1 + - regex: '; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/;\)]+)|)(?: + {0,2}Build|[;\)]| - )' + device_replacement: HTC $1 $2 + brand_replacement: HTC + model_replacement: $1 $2 + - regex: '; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ + _/]([^ _/;\)]+)|)|)(?: {0,2}Build|[;\)]| - )' + device_replacement: HTC $1 $2 $3 + brand_replacement: HTC + model_replacement: $1 $2 $3 + - regex: '; {0,2}(?:(?:HTC|htc)(?:_blocked|)[ _/])+([^ _/]+)(?:[ _/]([^ _/]+)(?:[ + _/]([^ _/]+)(?:[ _/]([^ /;]+)|)|)|)(?: {0,2}Build|[;\)]| - )' + device_replacement: HTC $1 $2 $3 $4 + brand_replacement: HTC + model_replacement: $1 $2 $3 $4 + - regex: HTC Streaming Player [^\/]{0,30}/[^\/]{0,10}/ htc_([^/]{1,10}) / + device_replacement: HTC $1 + brand_replacement: HTC + model_replacement: $1 + - regex: '(?:[;,] {0,2}|^)(?:htccn_chs-|)HTC[ _-]?([^;]{1,200}?)(?: {0,2}Build|clay|Android|-?Mozilla| + Opera| Profile| UNTRUSTED|[;/\(\)]|$)' + regex_flag: i + device_replacement: HTC $1 + brand_replacement: HTC + model_replacement: $1 + - regex: '; {0,2}(A6277|ADR6200|ADR6300|ADR6350|ADR6400[A-Z]*|ADR6425[A-Z]*|APX515CKT|ARIA|Desire[^_ + ]*|Dream|EndeavorU|Eris|Evo|Flyer|HD2|Hero|HERO200|Hero CDMA|HTL21|Incredible|Inspire[A-Z0-9]*|Legend|Liberty|Nexus + ?(?:One|HD2)|One|One S C2|One[ _]?(?:S|V|X\+?)\w*|PC36100|PG06100|PG86100|S31HT|Sensation|Wildfire)(?: + Build|[/;\(\)])' + regex_flag: i + device_replacement: HTC $1 + brand_replacement: HTC + model_replacement: $1 + - regex: ; {0,2}(ADR6200|ADR6400L|ADR6425LVW|Amaze|DesireS?|EndeavorU|Eris|EVO|Evo\d[A-Z]+|HD2|IncredibleS?|Inspire[A-Z0-9]*|Inspire[A-Z0-9]*|Sensation[A-Z0-9]*|Wildfire)[ + _-](.{1,200}?)(?:[/;\)]|Build|MIUI|1\.0) + regex_flag: i + device_replacement: HTC $1 $2 + brand_replacement: HTC + model_replacement: $1 $2 - regex: '; {0,2}HYUNDAI (T\d[^/]{0,10})(?: Build|\) AppleWebKit)' - device_replacement: 'Hyundai $1' - brand_replacement: 'Hyundai' - model_replacement: '$1' + device_replacement: Hyundai $1 + brand_replacement: Hyundai + model_replacement: $1 - regex: '; {0,2}HYUNDAI ([^;/]{1,10}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Hyundai $1' - brand_replacement: 'Hyundai' - model_replacement: '$1' - # X900? http://www.amazon.com/Hyundai-X900-Retina-Android-Bluetooth/dp/B00AO07H3O + device_replacement: Hyundai $1 + brand_replacement: Hyundai + model_replacement: $1 - regex: '; {0,2}(X700|Hold X|MB-6900)(?: Build|\) AppleWebKit)' - device_replacement: 'Hyundai $1' - brand_replacement: 'Hyundai' - model_replacement: '$1' - - ######### - # iBall - # @ref: http://www.iball.co.in/Category/Mobiles/22 - ######### + device_replacement: Hyundai $1 + brand_replacement: Hyundai + model_replacement: $1 - regex: '; {0,2}(?:iBall[ _\-]|)(Andi)[ _]?(\d[^;/]*)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'iBall' - model_replacement: '$1 $2' + regex_flag: i + device_replacement: $1 $2 + brand_replacement: iBall + model_replacement: $1 $2 - regex: '; {0,2}(IBall)(?:[ _]([^;/]{1,100}?)|)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'iBall' - model_replacement: '$2' - - ######### - # IconBIT - # @ref: http://www.iconbit.com/catalog/tablets/ - ######### - - regex: '; {0,2}(NT-\d+[^ ;/]{0,50}|Net[Tt]AB [^;/]{1,50}|Mercury [A-Z]{1,50}|iconBIT)(?: S/N:[^;/]{1,50}|)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'IconBIT' - model_replacement: '$1' - - ######### - # IMO - # @ref: http://www.ponselimo.com/ - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: iBall + model_replacement: $2 + - regex: '; {0,2}(NT-\d+[^ ;/]{0,50}|Net[Tt]AB [^;/]{1,50}|Mercury [A-Z]{1,50}|iconBIT)(?: + S/N:[^;/]{1,50}|)(?: Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: IconBIT + model_replacement: $1 - regex: '; {0,2}(IMO)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'IMO' - model_replacement: '$2' - - ######### - # i-mobile - # @ref: http://www.i-mobilephone.com/ - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: IMO + model_replacement: $2 - regex: '; {0,2}i-?mobile[ _]([^/]{1,50})(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'i-mobile $1' - brand_replacement: 'imobile' - model_replacement: '$1' + regex_flag: i + device_replacement: i-mobile $1 + brand_replacement: imobile + model_replacement: $1 - regex: '; {0,2}(i-(?:style|note)[^/]{0,10})(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'i-mobile $1' - brand_replacement: 'imobile' - model_replacement: '$1' - - ######### - # Impression - # @ref: http://impression.ua/planshetnye-kompyutery - ######### + regex_flag: i + device_replacement: i-mobile $1 + brand_replacement: imobile + model_replacement: $1 - regex: '; {0,2}(ImPAD) ?(\d+(?:.){0,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Impression' - model_replacement: '$1 $2' - - ######### - # Infinix - # @ref: http://www.infinixmobility.com/index.html - ######### + device_replacement: $1 $2 + brand_replacement: Impression + model_replacement: $1 $2 - regex: '; {0,2}(Infinix)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Infinix' - model_replacement: '$2' - - ######### - # Informer - # @ref: ?? - ######### + device_replacement: $1 $2 + brand_replacement: Infinix + model_replacement: $2 - regex: '; {0,2}(Informer)[ \-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Informer' - model_replacement: '$2' - - ######### - # Intenso - # @ref: http://www.intenso.de - # @models: 7":TAB 714,TAB 724;8":TAB 814,TAB 824;10":TAB 1004 - ######### + device_replacement: $1 $2 + brand_replacement: Informer + model_replacement: $2 - regex: '; {0,2}(TAB) ?([78][12]4)(?: Build|\) AppleWebKit)' - device_replacement: 'Intenso $1' - brand_replacement: 'Intenso' - model_replacement: '$1 $2' - - ######### - # Intex - # @ref: http://intexmobile.in/index.aspx - # @note: Zync also offers a "Cloud Z5" device - ######### - # smartphones - - regex: '; {0,2}(?:Intex[ _]|)(AQUA|Aqua)([ _\.\-])([^;/]{1,100}?) {0,2}(?:Build|;)' - device_replacement: '$1$2$3' - brand_replacement: 'Intex' - model_replacement: '$1 $3' - # matches "INTEX CLOUD X1" - - regex: '; {0,2}(?:INTEX|Intex)(?:[_ ]([^\ _;/]+))(?:[_ ]([^\ _;/]+)|) {0,2}(?:Build|;)' - device_replacement: '$1 $2' - brand_replacement: 'Intex' - model_replacement: '$1 $2' - # tablets - - regex: '; {0,2}([iI]Buddy)[ _]?(Connect)(?:_|\?_| |)([^;/]{0,50}) {0,2}(?:Build|;)' - device_replacement: '$1 $2 $3' - brand_replacement: 'Intex' - model_replacement: 'iBuddy $2 $3' - - regex: '; {0,2}(I-Buddy)[ _]([^;/]{1,100}?) {0,2}(?:Build|;)' - device_replacement: '$1 $2' - brand_replacement: 'Intex' - model_replacement: 'iBuddy $2' - - ######### - # iOCEAN - # @ref: http://www.iocean.cc/ - ######### + device_replacement: Intenso $1 + brand_replacement: Intenso + model_replacement: $1 $2 + - regex: ; {0,2}(?:Intex[ _]|)(AQUA|Aqua)([ _\.\-])([^;/]{1,100}?) {0,2}(?:Build|;) + device_replacement: $1$2$3 + brand_replacement: Intex + model_replacement: $1 $3 + - regex: ; {0,2}(?:INTEX|Intex)(?:[_ ]([^\ _;/]+))(?:[_ ]([^\ _;/]+)|) {0,2}(?:Build|;) + device_replacement: $1 $2 + brand_replacement: Intex + model_replacement: $1 $2 + - regex: ; {0,2}([iI]Buddy)[ _]?(Connect)(?:_|\?_| |)([^;/]{0,50}) {0,2}(?:Build|;) + device_replacement: $1 $2 $3 + brand_replacement: Intex + model_replacement: iBuddy $2 $3 + - regex: ; {0,2}(I-Buddy)[ _]([^;/]{1,100}?) {0,2}(?:Build|;) + device_replacement: $1 $2 + brand_replacement: Intex + model_replacement: iBuddy $2 - regex: '; {0,2}(iOCEAN) ([^/]{1,50})(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'iOCEAN' - model_replacement: '$2' - - ######### - # i.onik - # @ref: http://www.i-onik.de/ - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: iOCEAN + model_replacement: $2 - regex: '; {0,2}(TP\d+(?:\.\d+|)\-\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'ionik $1' - brand_replacement: 'ionik' - model_replacement: '$1' - - ######### - # IRU.ru - # @ref: http://www.iru.ru/catalog/soho/planetable/ - ######### + device_replacement: ionik $1 + brand_replacement: ionik + model_replacement: $1 - regex: '; {0,2}(M702pro)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Iru' - model_replacement: '$1' - - ######### - # Itel Mobile - # @ref: https://www.itel-mobile.com/global/products/ - ######### + device_replacement: $1 + brand_replacement: Iru + model_replacement: $1 - regex: '; {0,2}itel ([^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: 'Itel $1' - brand_replacement: 'Itel' - model_replacement: '$1' - - ######### - # Ivio - # @ref: http://www.ivio.com/mobile.php - # @models: DG80,DG20,DE38,DE88,MD70 - ######### + device_replacement: Itel $1 + brand_replacement: Itel + model_replacement: $1 - regex: '; {0,2}(DE88Plus|MD70)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Ivio' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Ivio + model_replacement: $1 - regex: '; {0,2}IVIO[_\-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Ivio' - model_replacement: '$1' - - ######### - # Jaytech - # @ref: http://www.jay-tech.de/jaytech/servlet/frontend/ - ######### + device_replacement: $1 + brand_replacement: Ivio + model_replacement: $1 - regex: '; {0,2}(TPC-\d+|JAY-TECH)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Jaytech' - model_replacement: '$1' - - ######### - # Jiayu - # @ref: http://www.ejiayu.com/en/Product.html - ######### + device_replacement: $1 + brand_replacement: Jaytech + model_replacement: $1 - regex: '; {0,2}(JY-[^;/]{1,100}|G[234]S?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Jiayu' - model_replacement: '$1' - - ######### - # JXD - # @ref: http://www.jxd.hk/ - ######### + device_replacement: $1 + brand_replacement: Jiayu + model_replacement: $1 - regex: '; {0,2}(JXD)[ _\-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'JXD' - model_replacement: '$2' - - ######### - # Karbonn - # @ref: http://www.karbonnmobiles.com/products_tablet.php - ######### - - regex: '; {0,2}Karbonn[ _]?([^;/]{1,100}) {0,2}(?:Build|;)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Karbonn' - model_replacement: '$1' - - regex: '; {0,2}([^;]{1,200}) Build/Karbonn' - device_replacement: '$1' - brand_replacement: 'Karbonn' - model_replacement: '$1' - - regex: '; {0,2}(A11|A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2|Titanium S\d) +Build' - device_replacement: '$1' - brand_replacement: 'Karbonn' - model_replacement: '$1' - - ######### - # KDDI (Operator Branded Device) - # @ref: http://www.ipentec.com/document/document.aspx?page=android-useragent - ######### + device_replacement: $1 $2 + brand_replacement: JXD + model_replacement: $2 + - regex: ; {0,2}Karbonn[ _]?([^;/]{1,100}) {0,2}(?:Build|;) + regex_flag: i + device_replacement: $1 + brand_replacement: Karbonn + model_replacement: $1 + - regex: ; {0,2}([^;]{1,200}) Build/Karbonn + device_replacement: $1 + brand_replacement: Karbonn + model_replacement: $1 + - regex: ; {0,2}(A11|A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2|Titanium S\d) + +Build + device_replacement: $1 + brand_replacement: Karbonn + model_replacement: $1 - regex: '; {0,2}(IS01|IS03|IS05|IS\d{2}SH)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Sharp' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Sharp + model_replacement: $1 - regex: '; {0,2}(IS04)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Regza' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Regza + model_replacement: $1 - regex: '; {0,2}(IS06|IS\d{2}PT)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Pantech' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Pantech + model_replacement: $1 - regex: '; {0,2}(IS11S)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'SonyEricsson' - model_replacement: 'Xperia Acro' + device_replacement: $1 + brand_replacement: SonyEricsson + model_replacement: Xperia Acro - regex: '; {0,2}(IS11CA)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Casio' - model_replacement: 'GzOne $1' + device_replacement: $1 + brand_replacement: Casio + model_replacement: GzOne $1 - regex: '; {0,2}(IS11LG)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'LG' - model_replacement: 'Optimus X' + device_replacement: $1 + brand_replacement: LG + model_replacement: Optimus X - regex: '; {0,2}(IS11N)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Medias' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Medias + model_replacement: $1 - regex: '; {0,2}(IS11PT)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Pantech' - model_replacement: 'MIRACH' + device_replacement: $1 + brand_replacement: Pantech + model_replacement: MIRACH - regex: '; {0,2}(IS12F)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Fujitsu' - model_replacement: 'Arrows ES' - # @ref: https://ja.wikipedia.org/wiki/IS12M + device_replacement: $1 + brand_replacement: Fujitsu + model_replacement: Arrows ES - regex: '; {0,2}(IS12M)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Motorola' - model_replacement: 'XT909' + device_replacement: $1 + brand_replacement: Motorola + model_replacement: XT909 - regex: '; {0,2}(IS12S)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'SonyEricsson' - model_replacement: 'Xperia Acro HD' + device_replacement: $1 + brand_replacement: SonyEricsson + model_replacement: Xperia Acro HD - regex: '; {0,2}(ISW11F)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Fujitsu' - model_replacement: 'Arrowz Z' + device_replacement: $1 + brand_replacement: Fujitsu + model_replacement: Arrowz Z - regex: '; {0,2}(ISW11HT)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: 'EVO' + device_replacement: $1 + brand_replacement: HTC + model_replacement: EVO - regex: '; {0,2}(ISW11K)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Kyocera' - model_replacement: 'DIGNO' + device_replacement: $1 + brand_replacement: Kyocera + model_replacement: DIGNO - regex: '; {0,2}(ISW11M)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Motorola' - model_replacement: 'Photon' + device_replacement: $1 + brand_replacement: Motorola + model_replacement: Photon - regex: '; {0,2}(ISW11SC)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Samsung' - model_replacement: 'GALAXY S II WiMAX' + device_replacement: $1 + brand_replacement: Samsung + model_replacement: GALAXY S II WiMAX - regex: '; {0,2}(ISW12HT)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: 'EVO 3D' + device_replacement: $1 + brand_replacement: HTC + model_replacement: EVO 3D - regex: '; {0,2}(ISW13HT)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: 'J' + device_replacement: $1 + brand_replacement: HTC + model_replacement: J - regex: '; {0,2}(ISW?[0-9]{2}[A-Z]{0,2})(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'KDDI' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: KDDI + model_replacement: $1 - regex: '; {0,2}(INFOBAR [^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'KDDI' - model_replacement: '$1' - - ######### - # Kingcom - # @ref: http://www.e-kingcom.com - ######### + device_replacement: $1 + brand_replacement: KDDI + model_replacement: $1 - regex: '; {0,2}(JOYPAD|Joypad)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Kingcom' - model_replacement: '$1 $2' - - ######### - # Kobo - # @ref: https://en.wikipedia.org/wiki/Kobo_Inc. - # @ref: http://www.kobo.com/devices#tablets - ######### + device_replacement: $1 $2 + brand_replacement: Kingcom + model_replacement: $1 $2 - regex: '; {0,2}(Vox|VOX|Arc|K080)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Kobo' - model_replacement: '$1' - - regex: '\b(Kobo Touch)\b' - device_replacement: '$1' - brand_replacement: 'Kobo' - model_replacement: '$1' - - ######### - # K-Touch - # @ref: ?? - ######### + regex_flag: i + device_replacement: $1 + brand_replacement: Kobo + model_replacement: $1 + - regex: \b(Kobo Touch)\b + device_replacement: $1 + brand_replacement: Kobo + model_replacement: $1 - regex: '; {0,2}(K-Touch)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'Ktouch' - model_replacement: '$2' - - ######### - # KT Tech - # @ref: http://www.kttech.co.kr - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: Ktouch + model_replacement: $2 - regex: '; {0,2}((?:EV|KM)-S\d+[A-Z]?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'KTtech' - model_replacement: '$1' - - ######### - # Kyocera - # @ref: http://www.android.com/devices/?country=all&m=kyocera - ######### - - regex: '; {0,2}(Zio|Hydro|Torque|Event|EVENT|Echo|Milano|Rise|URBANO PROGRESSO|WX04K|WX06K|WX10K|KYL21|101K|C5[12]\d{2})(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Kyocera' - model_replacement: '$1' - - ######### - # Lava - # @ref: http://www.lavamobiles.com/ - ######### - - regex: '; {0,2}(?:LAVA[ _]|)IRIS[ _\-]?([^/;\)]+) {0,2}(?:;|\)|Build)' - regex_flag: 'i' - device_replacement: 'Iris $1' - brand_replacement: 'Lava' - model_replacement: 'Iris $1' - - regex: '; {0,2}LAVA[ _]([^;/]{1,100}) Build' - device_replacement: '$1' - brand_replacement: 'Lava' - model_replacement: '$1' - - ######### - # Lemon - # @ref: http://www.lemonmobiles.com/products.php?type=1 - ######### - - regex: '; {0,2}(?:(Aspire A1)|(?:LEMON|Lemon)[ _]([^;/]{1,100}))_?(?: Build|\) AppleWebKit)' - device_replacement: 'Lemon $1$2' - brand_replacement: 'Lemon' - model_replacement: '$1$2' - - ######### - # Lenco - # @ref: http://www.lenco.com/c/tablets/ - ######### + regex_flag: i + device_replacement: $1 + brand_replacement: KTtech + model_replacement: $1 + - regex: '; {0,2}(Zio|Hydro|Torque|Event|EVENT|Echo|Milano|Rise|URBANO PROGRESSO|WX04K|WX06K|WX10K|KYL21|101K|C5[12]\d{2})(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Kyocera + model_replacement: $1 + - regex: ; {0,2}(?:LAVA[ _]|)IRIS[ _\-]?([^/;\)]+) {0,2}(?:;|\)|Build) + regex_flag: i + device_replacement: Iris $1 + brand_replacement: Lava + model_replacement: Iris $1 + - regex: ; {0,2}LAVA[ _]([^;/]{1,100}) Build + device_replacement: $1 + brand_replacement: Lava + model_replacement: $1 + - regex: '; {0,2}(?:(Aspire A1)|(?:LEMON|Lemon)[ _]([^;/]{1,100}))_?(?: Build|\) + AppleWebKit)' + device_replacement: Lemon $1$2 + brand_replacement: Lemon + model_replacement: $1$2 - regex: '; {0,2}(TAB-1012)(?: Build|\) AppleWebKit)' - device_replacement: 'Lenco $1' - brand_replacement: 'Lenco' - model_replacement: '$1' + device_replacement: Lenco $1 + brand_replacement: Lenco + model_replacement: $1 - regex: '; Lenco ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Lenco $1' - brand_replacement: 'Lenco' - model_replacement: '$1' - - ######### - # Lenovo - # @ref: http://support.lenovo.com/en_GB/downloads/default.page?# - ######### - - regex: '; {0,2}(A1_07|A2107A-H|S2005A-H|S1-37AH0) Build' - device_replacement: '$1' - brand_replacement: 'Lenovo' - model_replacement: '$1' - - regex: '; {0,2}(Idea[Tp]ab)[ _]([^;/]{1,100});? Build' - device_replacement: 'Lenovo $1 $2' - brand_replacement: 'Lenovo' - model_replacement: '$1 $2' - - regex: '; {0,2}(Idea(?:Tab|pad)) ?([^;/]{1,100}) Build' - device_replacement: 'Lenovo $1 $2' - brand_replacement: 'Lenovo' - model_replacement: '$1 $2' - - regex: '; {0,2}(ThinkPad) ?(Tablet) Build/' - device_replacement: 'Lenovo $1 $2' - brand_replacement: 'Lenovo' - model_replacement: '$1 $2' - - regex: '; {0,2}(?:LNV-|)(?:=?[Ll]enovo[ _\-]?|LENOVO[ _])(.{1,200}?)(?:Build|[;/\)])' - device_replacement: 'Lenovo $1' - brand_replacement: 'Lenovo' - model_replacement: '$1' + device_replacement: Lenco $1 + brand_replacement: Lenco + model_replacement: $1 + - regex: ; {0,2}(A1_07|A2107A-H|S2005A-H|S1-37AH0) Build + device_replacement: $1 + brand_replacement: Lenovo + model_replacement: $1 + - regex: ; {0,2}(Idea[Tp]ab)[ _]([^;/]{1,100});? Build + device_replacement: Lenovo $1 $2 + brand_replacement: Lenovo + model_replacement: $1 $2 + - regex: ; {0,2}(Idea(?:Tab|pad)) ?([^;/]{1,100}) Build + device_replacement: Lenovo $1 $2 + brand_replacement: Lenovo + model_replacement: $1 $2 + - regex: ; {0,2}(ThinkPad) ?(Tablet) Build/ + device_replacement: Lenovo $1 $2 + brand_replacement: Lenovo + model_replacement: $1 $2 + - regex: ; {0,2}(?:LNV-|)(?:=?[Ll]enovo[ _\-]?|LENOVO[ _])(.{1,200}?)(?:Build|[;/\)]) + device_replacement: Lenovo $1 + brand_replacement: Lenovo + model_replacement: $1 - regex: '[;,] (?:Vodafone |)(SmartTab) ?(II) ?(\d+) Build/' - device_replacement: 'Lenovo $1 $2 $3' - brand_replacement: 'Lenovo' - model_replacement: '$1 $2 $3' - - regex: '; {0,2}(?:Ideapad |)K1 Build/' - device_replacement: 'Lenovo Ideapad K1' - brand_replacement: 'Lenovo' - model_replacement: 'Ideapad K1' - - regex: '; {0,2}(3GC101|3GW10[01]|A390) Build/' - device_replacement: '$1' - brand_replacement: 'Lenovo' - model_replacement: '$1' - - regex: '\b(?:Lenovo|LENOVO)+[ _\-]?([^,;:/ ]+)' - device_replacement: 'Lenovo $1' - brand_replacement: 'Lenovo' - model_replacement: '$1' - - ######### - # Lexibook - # @ref: http://www.lexibook.com/fr - ######### + device_replacement: Lenovo $1 $2 $3 + brand_replacement: Lenovo + model_replacement: $1 $2 $3 + - regex: ; {0,2}(?:Ideapad |)K1 Build/ + device_replacement: Lenovo Ideapad K1 + brand_replacement: Lenovo + model_replacement: Ideapad K1 + - regex: ; {0,2}(3GC101|3GW10[01]|A390) Build/ + device_replacement: $1 + brand_replacement: Lenovo + model_replacement: $1 + - regex: \b(?:Lenovo|LENOVO)+[ _\-]?([^,;:/ ]+) + device_replacement: Lenovo $1 + brand_replacement: Lenovo + model_replacement: $1 - regex: '; {0,2}(MFC\d+)[A-Z]{2}([^;,/]*),?(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Lexibook' - model_replacement: '$1$2' - - ######### - # LG - # @ref: http://www.lg.com/uk/mobile - ######### - - regex: '; {0,2}(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]{1,30}|Nexus 4|Nexus 5X?|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) {0,2}(?:Build|;)' - device_replacement: '$1' - brand_replacement: 'LG' - model_replacement: '$1' + device_replacement: $1$2 + brand_replacement: Lexibook + model_replacement: $1$2 + - regex: ; {0,2}(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]{1,30}|Nexus 4|Nexus + 5X?|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) {0,2}(?:Build|;) + device_replacement: $1 + brand_replacement: LG + model_replacement: $1 - regex: '[;:] {0,2}(L-\d+[A-Z]|LGL\d+[A-Z]?)(?:/V\d+|) {0,2}(?:Build|[;\)])' - device_replacement: '$1' - brand_replacement: 'LG' - model_replacement: '$1' - - regex: '; {0,2}(LG-)([A-Z]{1,2}\d{2,}[^,;/\)\(]*?)(?:Build| V\d+|[,;/\)\(]|$)' - device_replacement: '$1$2' - brand_replacement: 'LG' - model_replacement: '$2' - - regex: '; {0,2}(LG[ \-]|LG)([^;/]{1,100})[;/]? Build' - device_replacement: '$1$2' - brand_replacement: 'LG' - model_replacement: '$2' - - regex: '^(LG)-([^;/]{1,100})/ Mozilla/.{0,200}; Android' - device_replacement: '$1 $2' - brand_replacement: 'LG' - model_replacement: '$2' - - regex: '(Web0S); Linux/(SmartTV)' - device_replacement: 'LG $1 $2' - brand_replacement: 'LG' - model_replacement: '$1 $2' - - ######### - # Malata - # @ref: http://www.malata.com/en/products.aspx?classid=680 - ######### + device_replacement: $1 + brand_replacement: LG + model_replacement: $1 + - regex: ; {0,2}(LG-)([A-Z]{1,2}\d{2,}[^,;/\)\(]*?)(?:Build| V\d+|[,;/\)\(]|$) + device_replacement: $1$2 + brand_replacement: LG + model_replacement: $2 + - regex: ; {0,2}(LG[ \-]|LG)([^;/]{1,100})[;/]? Build + device_replacement: $1$2 + brand_replacement: LG + model_replacement: $2 + - regex: ^(LG)-([^;/]{1,100})/ Mozilla/.{0,200}; Android + device_replacement: $1 $2 + brand_replacement: LG + model_replacement: $2 + - regex: (Web0S); Linux/(SmartTV) + device_replacement: LG $1 $2 + brand_replacement: LG + model_replacement: $1 $2 - regex: '; {0,2}((?:SMB|smb)[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Malata' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Malata + model_replacement: $1 - regex: '; {0,2}(?:Malata|MALATA) ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Malata' - model_replacement: '$1' - - ######### - # Manta - # @ref: http://www.manta.com.pl/en - ######### - - regex: '; {0,2}(MS[45][0-9]{3}|MID0[568][NS]?|MID[1-9]|MID[78]0[1-9]|MID970[1-9]|MID100[1-9])(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Manta' - model_replacement: '$1' - - ######### - # Match - # @ref: http://www.match.net.cn/products.asp - ######### - - regex: '; {0,2}(M1052|M806|M9000|M9100|M9701|MID100|MID120|MID125|MID130|MID135|MID140|MID701|MID710|MID713|MID727|MID728|MID731|MID732|MID733|MID735|MID736|MID737|MID760|MID800|MID810|MID820|MID830|MID833|MID835|MID860|MID900|MID930|MID933|MID960|MID980)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Match' - model_replacement: '$1' - - ######### - # Maxx - # @ref: http://www.maxxmobile.in/ - # @models: Maxx MSD7-Play, Maxx MX245+ Trance, Maxx AX8 Race, Maxx MSD7 3G- AX50, Maxx Genx Droid 7 - AX40, Maxx AX5 Duo, - # Maxx AX3 Duo, Maxx AX3, Maxx AX8 Note II (Note 2), Maxx AX8 Note I, Maxx AX8, Maxx AX5 Plus, Maxx MSD7 Smarty, - # Maxx AX9Z Race, - # Maxx MT150, Maxx MQ601, Maxx M2020, Maxx Sleek MX463neo, Maxx MX525, Maxx MX192-Tune, Maxx Genx Droid 7 AX353, - # @note: Need more User-Agents!!! - ######### - - regex: '; {0,2}(GenxDroid7|MSD7.{0,200}?|AX\d.{0,200}?|Tab 701|Tab 722)(?: Build|\) AppleWebKit)' - device_replacement: 'Maxx $1' - brand_replacement: 'Maxx' - model_replacement: '$1' - - ######### - # Mediacom - # @ref: http://www.mediacomeurope.it/ - ######### + device_replacement: $1 + brand_replacement: Malata + model_replacement: $1 + - regex: '; {0,2}(MS[45][0-9]{3}|MID0[568][NS]?|MID[1-9]|MID[78]0[1-9]|MID970[1-9]|MID100[1-9])(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Manta + model_replacement: $1 + - regex: '; {0,2}(M1052|M806|M9000|M9100|M9701|MID100|MID120|MID125|MID130|MID135|MID140|MID701|MID710|MID713|MID727|MID728|MID731|MID732|MID733|MID735|MID736|MID737|MID760|MID800|MID810|MID820|MID830|MID833|MID835|MID860|MID900|MID930|MID933|MID960|MID980)(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Match + model_replacement: $1 + - regex: '; {0,2}(GenxDroid7|MSD7.{0,200}?|AX\d.{0,200}?|Tab 701|Tab 722)(?: Build|\) + AppleWebKit)' + device_replacement: Maxx $1 + brand_replacement: Maxx + model_replacement: $1 - regex: '; {0,2}(M-PP[^;/]{1,30}|PhonePad ?\d{2,}[^;/]{1,30}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Mediacom $1' - brand_replacement: 'Mediacom' - model_replacement: '$1' + device_replacement: Mediacom $1 + brand_replacement: Mediacom + model_replacement: $1 - regex: '; {0,2}(M-MP[^;/]{1,30}|SmartPad ?\d{2,}[^;/]{1,30}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Mediacom $1' - brand_replacement: 'Mediacom' - model_replacement: '$1' - - ######### - # Medion - # @ref: http://www.medion.com/en/ - ######### + device_replacement: Mediacom $1 + brand_replacement: Mediacom + model_replacement: $1 - regex: '; {0,2}(?:MD_|)LIFETAB[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Medion Lifetab $1' - brand_replacement: 'Medion' - model_replacement: 'Lifetab $1' + regex_flag: i + device_replacement: Medion Lifetab $1 + brand_replacement: Medion + model_replacement: Lifetab $1 - regex: '; {0,2}MEDION ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Medion $1' - brand_replacement: 'Medion' - model_replacement: '$1' - - ######### - # Meizu - # @ref: http://www.meizu.com - ######### + device_replacement: Medion $1 + brand_replacement: Medion + model_replacement: $1 - regex: '; {0,2}(M030|M031|M035|M040|M065|m9)(?: Build|\) AppleWebKit)' - device_replacement: 'Meizu $1' - brand_replacement: 'Meizu' - model_replacement: '$1' - - regex: '; {0,2}(?:meizu_|MEIZU )(.{1,200}?) {0,2}(?:Build|[;\)])' - device_replacement: 'Meizu $1' - brand_replacement: 'Meizu' - model_replacement: '$1' - - ######### - # Meta - # @ref: https://www.meta.com - ######### - - regex: 'Quest 2' - device_replacement: 'Quest' - brand_replacement: 'Meta' - model_replacement: 'Quest 2' - - - regex: 'Quest Pro' - device_replacement: 'Quest' - brand_replacement: 'Meta' - model_replacement: 'Quest Pro' - - - regex: 'Quest' - device_replacement: 'Quest' - brand_replacement: 'Meta' - model_replacement: 'Quest' - - ######### - # Micromax - # @ref: http://www.micromaxinfo.com - ######### - - regex: '; {0,2}(?:Micromax[ _](A111|A240)|(A111|A240)) Build' - regex_flag: 'i' - device_replacement: 'Micromax $1$2' - brand_replacement: 'Micromax' - model_replacement: '$1$2' - - regex: '; {0,2}Micromax[ _](A\d{2,3}[^;/]*) Build' - regex_flag: 'i' - device_replacement: 'Micromax $1' - brand_replacement: 'Micromax' - model_replacement: '$1' - # be carefull here with Acer e.g. A500 - - regex: '; {0,2}(A\d{2}|A[12]\d{2}|A90S|A110Q) Build' - regex_flag: 'i' - device_replacement: 'Micromax $1' - brand_replacement: 'Micromax' - model_replacement: '$1' - - regex: '; {0,2}Micromax[ _](P\d{3}[^;/]*) Build' - regex_flag: 'i' - device_replacement: 'Micromax $1' - brand_replacement: 'Micromax' - model_replacement: '$1' - - regex: '; {0,2}(P\d{3}|P\d{3}\(Funbook\)) Build' - regex_flag: 'i' - device_replacement: 'Micromax $1' - brand_replacement: 'Micromax' - model_replacement: '$1' - - ######### - # Mito - # @ref: http://new.mitomobile.com/ - ######### + device_replacement: Meizu $1 + brand_replacement: Meizu + model_replacement: $1 + - regex: ; {0,2}(?:meizu_|MEIZU )(.{1,200}?) {0,2}(?:Build|[;\)]) + device_replacement: Meizu $1 + brand_replacement: Meizu + model_replacement: $1 + - regex: Quest 3 + device_replacement: Quest + brand_replacement: Meta + model_replacement: Quest 3 + - regex: Quest 2 + device_replacement: Quest + brand_replacement: Meta + model_replacement: Quest 2 + - regex: Quest Pro + device_replacement: Quest + brand_replacement: Meta + model_replacement: Quest Pro + - regex: Quest + device_replacement: Quest + brand_replacement: Meta + model_replacement: Quest + - regex: ; {0,2}(?:Micromax[ _](A111|A240)|(A111|A240)) Build + regex_flag: i + device_replacement: Micromax $1$2 + brand_replacement: Micromax + model_replacement: $1$2 + - regex: ; {0,2}Micromax[ _](A\d{2,3}[^;/]*) Build + regex_flag: i + device_replacement: Micromax $1 + brand_replacement: Micromax + model_replacement: $1 + - regex: ; {0,2}(A\d{2}|A[12]\d{2}|A90S|A110Q) Build + regex_flag: i + device_replacement: Micromax $1 + brand_replacement: Micromax + model_replacement: $1 + - regex: ; {0,2}Micromax[ _](P\d{3}[^;/]*) Build + regex_flag: i + device_replacement: Micromax $1 + brand_replacement: Micromax + model_replacement: $1 + - regex: ; {0,2}(P\d{3}|P\d{3}\(Funbook\)) Build + regex_flag: i + device_replacement: Micromax $1 + brand_replacement: Micromax + model_replacement: $1 - regex: '; {0,2}(MITO)[ _\-]?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'Mito' - model_replacement: '$2' - - ######### - # Mobistel - # @ref: http://www.mobistel.com/ - ######### - - regex: '; {0,2}(Cynus)[ _](F5|T\d|.{1,200}?) {0,2}(?:Build|[;/\)])' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'Mobistel' - model_replacement: '$1 $2' - - ######### - # Modecom - # @ref: http://www.modecom.eu/tablets/portal/ - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: Mito + model_replacement: $2 + - regex: ; {0,2}(Cynus)[ _](F5|T\d|.{1,200}?) {0,2}(?:Build|[;/\)]) + regex_flag: i + device_replacement: $1 $2 + brand_replacement: Mobistel + model_replacement: $1 $2 - regex: '; {0,2}(MODECOM |)(FreeTab) ?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1$2 $3' - brand_replacement: 'Modecom' - model_replacement: '$2 $3' + regex_flag: i + device_replacement: $1$2 $3 + brand_replacement: Modecom + model_replacement: $2 $3 - regex: '; {0,2}(MODECOM )([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'Modecom' - model_replacement: '$2' - - ######### - # Motorola - # @ref: http://www.motorola.com/us/shop-all-mobile-phones/ - ######### - - regex: '; {0,2}(MZ\d{3}\+?|MZ\d{3} 4G|Xoom|XOOM[^;/]*) Build' - device_replacement: 'Motorola $1' - brand_replacement: 'Motorola' - model_replacement: '$1' - - regex: '; {0,2}(Milestone )(XT[^;/]*) Build' - device_replacement: 'Motorola $1$2' - brand_replacement: 'Motorola' - model_replacement: '$2' - - regex: '; {0,2}(Motoroi ?x|Droid X|DROIDX) Build' - regex_flag: 'i' - device_replacement: 'Motorola $1' - brand_replacement: 'Motorola' - model_replacement: 'DROID X' - - regex: '; {0,2}(Droid[^;/]*|DROID[^;/]*|Milestone[^;/]*|Photon|Triumph|Devour|Titanium) Build' - device_replacement: 'Motorola $1' - brand_replacement: 'Motorola' - model_replacement: '$1' - - regex: '; {0,2}(A555|A85[34][^;/]*|A95[356]|ME[58]\d{2}\+?|ME600|ME632|ME722|MB\d{3}\+?|MT680|MT710|MT870|MT887|MT917|WX435|WX453|WX44[25]|XT\d{3,4}[A-Z\+]*|CL[iI]Q|CL[iI]Q XT) Build' - device_replacement: '$1' - brand_replacement: 'Motorola' - model_replacement: '$1' - - regex: '; {0,2}(Motorola MOT-|Motorola[ _\-]|MOT\-?)([^;/]{1,100}) Build' - device_replacement: '$1$2' - brand_replacement: 'Motorola' - model_replacement: '$2' - - regex: '; {0,2}(Moto[_ ]?|MOT\-)([^;/]{1,100}) Build' - device_replacement: '$1$2' - brand_replacement: 'Motorola' - model_replacement: '$2' - - ######### - # MpMan - # @ref: http://www.mpmaneurope.com - ######### - - regex: '; {0,2}((?:MP[DQ]C|MPG\d{1,4}|MP\d{3,4}|MID(?:(?:10[234]|114|43|7[247]|8[24]|7)C|8[01]1))[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Mpman' - model_replacement: '$1' - - ######### - # MSI - # @ref: http://www.msi.com/product/windpad/ - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: Modecom + model_replacement: $2 + - regex: ; {0,2}(MZ\d{3}\+?|MZ\d{3} 4G|Xoom|XOOM[^;/]*) Build + device_replacement: Motorola $1 + brand_replacement: Motorola + model_replacement: $1 + - regex: ; {0,2}(Milestone )(XT[^;/]*) Build + device_replacement: Motorola $1$2 + brand_replacement: Motorola + model_replacement: $2 + - regex: ; {0,2}(Motoroi ?x|Droid X|DROIDX) Build + regex_flag: i + device_replacement: Motorola $1 + brand_replacement: Motorola + model_replacement: DROID X + - regex: ; {0,2}(Droid[^;/]*|DROID[^;/]*|Milestone[^;/]*|Photon|Triumph|Devour|Titanium) + Build + device_replacement: Motorola $1 + brand_replacement: Motorola + model_replacement: $1 + - regex: ; {0,2}(A555|A85[34][^;/]*|A95[356]|ME[58]\d{2}\+?|ME600|ME632|ME722|MB\d{3}\+?|MT680|MT710|MT870|MT887|MT917|WX435|WX453|WX44[25]|XT\d{3,4}[A-Z\+]*|CL[iI]Q|CL[iI]Q + XT) Build + device_replacement: $1 + brand_replacement: Motorola + model_replacement: $1 + - regex: ; {0,2}(Motorola MOT-|Motorola[ _\-]|MOT\-?)([^;/]{1,100}) Build + device_replacement: $1$2 + brand_replacement: Motorola + model_replacement: $2 + - regex: ; {0,2}(Moto[_ ]?|MOT\-)([^;/]{1,100}) Build + device_replacement: $1$2 + brand_replacement: Motorola + model_replacement: $2 + - regex: '; {0,2}((?:MP[DQ]C|MPG\d{1,4}|MP\d{3,4}|MID(?:(?:10[234]|114|43|7[247]|8[24]|7)C|8[01]1))[^;/]*)(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Mpman + model_replacement: $1 - regex: '; {0,2}(?:MSI[ _]|)(Primo\d+|Enjoy[ _\-][^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'Msi' - model_replacement: '$1' - - ######### - # Multilaser - # http://www.multilaser.com.br/listagem_produtos.php?cat=5 - ######### + regex_flag: i + device_replacement: $1 + brand_replacement: Msi + model_replacement: $1 - regex: '; {0,2}Multilaser[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Multilaser' - model_replacement: '$1' - - ######### - # MyPhone - # @ref: http://myphone.com.ph/ - ######### + device_replacement: $1 + brand_replacement: Multilaser + model_replacement: $1 - regex: '; {0,2}(My)[_]?(Pad)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2 $3' - brand_replacement: 'MyPhone' - model_replacement: '$1$2 $3' + device_replacement: $1$2 $3 + brand_replacement: MyPhone + model_replacement: $1$2 $3 - regex: '; {0,2}(My)\|?(Phone)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2 $3' - brand_replacement: 'MyPhone' - model_replacement: '$3' + device_replacement: $1$2 $3 + brand_replacement: MyPhone + model_replacement: $3 - regex: '; {0,2}(A\d+)[ _](Duo|)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'MyPhone' - model_replacement: '$1 $2' - - ######### - # Mytab - # @ref: http://www.mytab.eu/en/category/mytab-products/ - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: MyPhone + model_replacement: $1 $2 - regex: '; {0,2}(myTab[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Mytab' - model_replacement: '$1' - - ######### - # Nabi - # @ref: https://www.nabitablet.com - ######### + device_replacement: $1 + brand_replacement: Mytab + model_replacement: $1 - regex: '; {0,2}(NABI2?-)([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Nabi' - model_replacement: '$2' - - ######### - # Nec Medias - # @ref: http://www.n-keitai.com/ - ######### + device_replacement: $1$2 + brand_replacement: Nabi + model_replacement: $2 - regex: '; {0,2}(N-\d+[CDE])(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Nec' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Nec + model_replacement: $1 - regex: '; ?(NEC-)(.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Nec' - model_replacement: '$2' + device_replacement: $1$2 + brand_replacement: Nec + model_replacement: $2 - regex: '; {0,2}(LT-NA7)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Nec' - model_replacement: 'Lifetouch Note' - - ######### - # Nextbook - # @ref: http://nextbookusa.com - ######### - - regex: '; {0,2}(NXM\d+[A-Za-z0-9_]{0,50}|Next\d[A-Za-z0-9_ \-]{0,50}|NEXT\d[A-Za-z0-9_ \-]{0,50}|Nextbook [A-Za-z0-9_ ]{0,50}|DATAM803HC|M805)(?: Build|[\);])' - device_replacement: '$1' - brand_replacement: 'Nextbook' - model_replacement: '$1' - - ######### - # Nokia - # @ref: http://www.nokia.com - ######### - - regex: '; {0,2}(Nokia)([ _\-]{0,5})([^;/]{0,50}) Build' - regex_flag: 'i' - device_replacement: '$1$2$3' - brand_replacement: 'Nokia' - model_replacement: '$3' + device_replacement: $1 + brand_replacement: Nec + model_replacement: Lifetouch Note + - regex: '; {0,2}(NXM\d+[A-Za-z0-9_]{0,50}|Next\d[A-Za-z0-9_ \-]{0,50}|NEXT\d[A-Za-z0-9_ + \-]{0,50}|Nextbook [A-Za-z0-9_ ]{0,50}|DATAM803HC|M805)(?: Build|[\);])' + device_replacement: $1 + brand_replacement: Nextbook + model_replacement: $1 + - regex: ; {0,2}(Nokia)([ _\-]{0,5})([^;/]{0,50}) Build + regex_flag: i + device_replacement: $1$2$3 + brand_replacement: Nokia + model_replacement: $3 - regex: '; {0,2}(TA\-\d{4})(?: Build|\) AppleWebKit)' - device_replacement: 'Nokia $1' - brand_replacement: 'Nokia' - model_replacement: '$1' - - ######### - # Nook - # @ref: - # TODO nook browser/1.0 - ######### + device_replacement: Nokia $1 + brand_replacement: Nokia + model_replacement: $1 - regex: '; {0,2}(Nook ?|Barnes & Noble Nook |BN )([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Nook' - model_replacement: '$2' - - regex: '; {0,2}(NOOK |)(BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Nook' - model_replacement: '$2' - - regex: '; Build/(Nook)' - device_replacement: '$1' - brand_replacement: 'Nook' - model_replacement: 'Tablet' - - ######### - # Olivetti - # @ref: http://www.olivetti.de/EN/Page/t02/view_html?idp=348 - ######### + device_replacement: $1$2 + brand_replacement: Nook + model_replacement: $2 + - regex: '; {0,2}(NOOK |)(BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD + Zoom2)(?: Build|\) AppleWebKit)' + device_replacement: $1$2 + brand_replacement: Nook + model_replacement: $2 + - regex: ; Build/(Nook) + device_replacement: $1 + brand_replacement: Nook + model_replacement: Tablet - regex: '; {0,2}(OP110|OliPad[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Olivetti $1' - brand_replacement: 'Olivetti' - model_replacement: '$1' - - ######### - # Omega - # @ref: http://omega-technology.eu/en/produkty/346/tablets - # @note: MID tablets might get matched by CobyKyros first - # @models: (T107|MID(?:700[2-5]|7031|7108|7132|750[02]|8001|8500|9001|971[12]) - ######### + device_replacement: Olivetti $1 + brand_replacement: Olivetti + model_replacement: $1 - regex: '; {0,2}OMEGA[ _\-](MID[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Omega $1' - brand_replacement: 'Omega' - model_replacement: '$1' - - regex: '^(MID7500|MID\d+) Mozilla/5\.0 \(iPad;' - device_replacement: 'Omega $1' - brand_replacement: 'Omega' - model_replacement: '$1' - - ######### - # OpenPeak - # @ref: https://support.google.com/googleplay/answer/1727131?hl=en - ######### + device_replacement: Omega $1 + brand_replacement: Omega + model_replacement: $1 + - regex: ^(MID7500|MID\d+) Mozilla/5\.0 \(iPad; + device_replacement: Omega $1 + brand_replacement: Omega + model_replacement: $1 - regex: '; {0,2}((?:CIUS|cius)[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: 'Openpeak $1' - brand_replacement: 'Openpeak' - model_replacement: '$1' - - ######### - # Oppo - # @ref: http://en.oppo.com/products/ - ######### - - regex: '; {0,2}(Find ?(?:5|7a)|R8[012]\d{1,2}|T703\d?|U70\d{1,2}T?|X90\d{1,2}|[AFR]\d{1,2}[a-z]{1,2})(?: Build|\) AppleWebKit)' - device_replacement: 'Oppo $1' - brand_replacement: 'Oppo' - model_replacement: '$1' + device_replacement: Openpeak $1 + brand_replacement: Openpeak + model_replacement: $1 + - regex: '; {0,2}(Find ?(?:5|7a)|R8[012]\d{1,2}|T703\d?|U70\d{1,2}T?|X90\d{1,2}|[AFR]\d{1,2}[a-z]{1,2})(?: + Build|\) AppleWebKit)' + device_replacement: Oppo $1 + brand_replacement: Oppo + model_replacement: $1 - regex: '; {0,2}OPPO ?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Oppo $1' - brand_replacement: 'Oppo' - model_replacement: '$1' + device_replacement: Oppo $1 + brand_replacement: Oppo + model_replacement: $1 - regex: '; {0,2}(CPH\d{1,4}|RMX\d{1,4}|P[A-Z]{3}\d{2})(?: Build|\) AppleWebKit)' - device_replacement: 'Oppo $1' - brand_replacement: 'Oppo' + device_replacement: Oppo $1 + brand_replacement: Oppo - regex: '; {0,2}(A1601)(?: Build|\) AppleWebKit)' - device_replacement: 'Oppo F1s' - brand_replacement: 'Oppo' - model_replacement: '$1' - - ######### - # Odys - # @ref: http://odys.de - ######### + device_replacement: Oppo F1s + brand_replacement: Oppo + model_replacement: $1 - regex: '; {0,2}(?:Odys\-|ODYS\-|ODYS )([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Odys $1' - brand_replacement: 'Odys' - model_replacement: '$1' + device_replacement: Odys $1 + brand_replacement: Odys + model_replacement: $1 - regex: '; {0,2}(SELECT) ?(7)(?: Build|\) AppleWebKit)' - device_replacement: 'Odys $1 $2' - brand_replacement: 'Odys' - model_replacement: '$1 $2' + device_replacement: Odys $1 $2 + brand_replacement: Odys + model_replacement: $1 $2 - regex: '; {0,2}(PEDI)_(PLUS)_(W)(?: Build|\) AppleWebKit)' - device_replacement: 'Odys $1 $2 $3' - brand_replacement: 'Odys' - model_replacement: '$1 $2 $3' - # Weltbild - Tablet PC 4 = Cat Phoenix = Odys Tablet PC 4? - - regex: '; {0,2}(AEON|BRAVIO|FUSION|FUSION2IN1|Genio|EOS10|IEOS[^;/]*|IRON|Loox|LOOX|LOOX Plus|Motion|NOON|NOON_PRO|NEXT|OPOS|PEDI[^;/]*|PRIME[^;/]*|STUDYTAB|TABLO|Tablet-PC-4|UNO_X8|XELIO[^;/]*|Xelio ?\d+ ?[Pp]ro|XENO10|XPRESS PRO)(?: Build|\) AppleWebKit)' - device_replacement: 'Odys $1' - brand_replacement: 'Odys' - model_replacement: '$1' - - ######### - # OnePlus - # @ref https://oneplus.net/ - ######### + device_replacement: Odys $1 $2 $3 + brand_replacement: Odys + model_replacement: $1 $2 $3 + - regex: '; {0,2}(AEON|BRAVIO|FUSION|FUSION2IN1|Genio|EOS10|IEOS[^;/]*|IRON|Loox|LOOX|LOOX + Plus|Motion|NOON|NOON_PRO|NEXT|OPOS|PEDI[^;/]*|PRIME[^;/]*|STUDYTAB|TABLO|Tablet-PC-4|UNO_X8|XELIO[^;/]*|Xelio + ?\d+ ?[Pp]ro|XENO10|XPRESS PRO)(?: Build|\) AppleWebKit)' + device_replacement: Odys $1 + brand_replacement: Odys + model_replacement: $1 - regex: '; (ONE [a-zA-Z]\d+)(?: Build|\) AppleWebKit)' - device_replacement: 'OnePlus $1' - brand_replacement: 'OnePlus' - model_replacement: '$1' + device_replacement: OnePlus $1 + brand_replacement: OnePlus + model_replacement: $1 - regex: '; (ONEPLUS [a-zA-Z]\d+)(?: Build|\) AppleWebKit)' - device_replacement: 'OnePlus $1' - brand_replacement: 'OnePlus' - model_replacement: '$1' - - regex: '; {0,2}(HD1903|GM1917|IN2025|LE2115|LE2127|HD1907|BE2012|BE2025|BE2026|BE2028|BE2029|DE2117|DE2118|EB2101|GM1900|GM1910|GM1915|HD1905|HD1925|IN2015|IN2017|IN2019|KB2005|KB2007|LE2117|LE2125|BE2015|GM1903|HD1900|HD1901|HD1910|HD1913|IN2010|IN2013|IN2020|LE2111|LE2120|LE2121|LE2123|BE2011|IN2023|KB2003|LE2113|NE2215|DN2101)(?: Build|\) AppleWebKit)' - device_replacement: 'OnePlus $1' - brand_replacement: 'OnePlus' - model_replacement: 'OnePlus $1' + device_replacement: OnePlus $1 + brand_replacement: OnePlus + model_replacement: $1 + - regex: '; {0,2}(HD1903|GM1917|IN2025|LE2115|LE2127|HD1907|BE2012|BE2025|BE2026|BE2028|BE2029|DE2117|DE2118|EB2101|GM1900|GM1910|GM1915|HD1905|HD1925|IN2015|IN2017|IN2019|KB2005|KB2007|LE2117|LE2125|BE2015|GM1903|HD1900|HD1901|HD1910|HD1913|IN2010|IN2013|IN2020|LE2111|LE2120|LE2121|LE2123|BE2011|IN2023|KB2003|LE2113|NE2215|DN2101)(?: + Build|\) AppleWebKit)' + device_replacement: OnePlus $1 + brand_replacement: OnePlus + model_replacement: OnePlus $1 - regex: '; (OnePlus[ a-zA-z0-9]{0,50});((?: Build|.{0,50}\) AppleWebKit))' - device_replacement: '$1' - brand_replacement: 'OnePlus' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: OnePlus + model_replacement: $1 - regex: '; (OnePlus[ a-zA-z0-9]{0,50})((?: Build|\) AppleWebKit))' - device_replacement: '$1' - brand_replacement: 'OnePlus' - model_replacement: '$1' - - ######### - # Orion - # @ref: http://www.orion.ua/en/products/computer-products/tablet-pcs.html - ######### + device_replacement: $1 + brand_replacement: OnePlus + model_replacement: $1 - regex: '; {0,2}(TP-\d+)(?: Build|\) AppleWebKit)' - device_replacement: 'Orion $1' - brand_replacement: 'Orion' - model_replacement: '$1' - - ######### - # PackardBell - # @ref: http://www.packardbell.com/pb/en/AE/content/productgroup/tablets - ######### + device_replacement: Orion $1 + brand_replacement: Orion + model_replacement: $1 - regex: '; {0,2}(G100W?)(?: Build|\) AppleWebKit)' - device_replacement: 'PackardBell $1' - brand_replacement: 'PackardBell' - model_replacement: '$1' - - ######### - # Panasonic - # @ref: http://panasonic.jp/mobile/ - # @models: T11, T21, T31, P11, P51, Eluga Power, Eluga DL1 - # @models: (tab) Toughpad FZ-A1, Toughpad JT-B1 - ######### + device_replacement: PackardBell $1 + brand_replacement: PackardBell + model_replacement: $1 - regex: '; {0,2}(Panasonic)[_ ]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - # Toughpad + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}(FZ-A1B|JT-B1)(?: Build|\) AppleWebKit)' - device_replacement: 'Panasonic $1' - brand_replacement: 'Panasonic' - model_replacement: '$1' - # Eluga Power + device_replacement: Panasonic $1 + brand_replacement: Panasonic + model_replacement: $1 - regex: '; {0,2}(dL1|DL1)(?: Build|\) AppleWebKit)' - device_replacement: 'Panasonic $1' - brand_replacement: 'Panasonic' - model_replacement: '$1' - - ######### - # Pantech - # @href: http://www.pantech.co.kr/en/prod/prodList.do?gbrand=PANTECH - # @href: http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA - # @models: ADR8995, ADR910L, ADR930VW, C790, CDM8992, CDM8999, IS06, IS11PT, P2000, P2020, P2030, P4100, P5000, P6010, P6020, P6030, P7000, P7040, P8000, P8010, P9020, P9050, P9060, P9070, P9090, PT001, PT002, PT003, TXT8040, TXT8045, VEGA PTL21 - ######### - - regex: '; {0,2}(SKY[ _]|)(IM\-[AT]\d{3}[^;/]{1,100}).{0,30} Build/' - device_replacement: 'Pantech $1$2' - brand_replacement: 'Pantech' - model_replacement: '$1$2' + device_replacement: Panasonic $1 + brand_replacement: Panasonic + model_replacement: $1 + - regex: ; {0,2}(SKY[ _]|)(IM\-[AT]\d{3}[^;/]{1,100}).{0,30} Build/ + device_replacement: Pantech $1$2 + brand_replacement: Pantech + model_replacement: $1$2 - regex: '; {0,2}((?:ADR8995|ADR910L|ADR930L|ADR930VW|PTL21|P8000)(?: 4G|)) Build/' - device_replacement: '$1' - brand_replacement: 'Pantech' - model_replacement: '$1' - - regex: '; {0,2}Pantech([^;/]{1,30}).{0,200}? Build/' - device_replacement: 'Pantech $1' - brand_replacement: 'Pantech' - model_replacement: '$1' - - ######### - # Papayre - # @ref: http://grammata.es/ - ######### + device_replacement: $1 + brand_replacement: Pantech + model_replacement: $1 + - regex: ; {0,2}Pantech([^;/]{1,30}).{0,200}? Build/ + device_replacement: Pantech $1 + brand_replacement: Pantech + model_replacement: $1 - regex: '; {0,2}(papyre)[ _\-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'Papyre' - model_replacement: '$2' - - ######### - # Pearl - # @ref: http://www.pearl.de/c-1540.shtml - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: Papyre + model_replacement: $2 - regex: '; {0,2}(?:Touchlet )?(X10\.[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Pearl $1' - brand_replacement: 'Pearl' - model_replacement: '$1' - - ######### - # Phicomm - # @ref: http://www.phicomm.com.cn/ - ######### + device_replacement: Pearl $1 + brand_replacement: Pearl + model_replacement: $1 - regex: '; PHICOMM (i800)(?: Build|\) AppleWebKit)' - device_replacement: 'Phicomm $1' - brand_replacement: 'Phicomm' - model_replacement: '$1' + device_replacement: Phicomm $1 + brand_replacement: Phicomm + model_replacement: $1 - regex: '; PHICOMM ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Phicomm $1' - brand_replacement: 'Phicomm' - model_replacement: '$1' + device_replacement: Phicomm $1 + brand_replacement: Phicomm + model_replacement: $1 - regex: '; {0,2}(FWS\d{3}[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Phicomm $1' - brand_replacement: 'Phicomm' - model_replacement: '$1' - - ######### - # Philips - # @ref: http://www.support.philips.com/support/catalog/products.jsp?_dyncharset=UTF-8&country=&categoryid=MOBILE_PHONES_SMART_SU_CN_CARE&userLanguage=en&navCount=2&groupId=PC_PRODUCTS_AND_PHONES_GR_CN_CARE&catalogType=&navAction=push&userCountry=cn&title=Smartphones&cateId=MOBILE_PHONES_CA_CN_CARE - # @TODO: Philips Tablets User-Agents missing! - # @ref: http://www.support.philips.com/support/catalog/products.jsp?_dyncharset=UTF-8&country=&categoryid=ENTERTAINMENT_TABLETS_SU_CN_CARE&userLanguage=en&navCount=0&groupId=&catalogType=&navAction=push&userCountry=cn&title=Entertainment+Tablets&cateId=TABLETS_CA_CN_CARE - ######### - # @note: this a best guess according to available philips models. Need more User-Agents - - regex: '; {0,2}(D633|D822|D833|T539|T939|V726|W335|W336|W337|W3568|W536|W5510|W626|W632|W6350|W6360|W6500|W732|W736|W737|W7376|W820|W832|W8355|W8500|W8510|W930)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Philips' - model_replacement: '$1' + device_replacement: Phicomm $1 + brand_replacement: Phicomm + model_replacement: $1 + - regex: '; {0,2}(D633|D822|D833|T539|T939|V726|W335|W336|W337|W3568|W536|W5510|W626|W632|W6350|W6360|W6500|W732|W736|W737|W7376|W820|W832|W8355|W8500|W8510|W930)(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Philips + model_replacement: $1 - regex: '; {0,2}(?:Philips|PHILIPS)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Philips $1' - brand_replacement: 'Philips' - model_replacement: '$1' - - ######### - # Pipo - # @ref: http://www.pipo.cn/En/ - ######### - - regex: 'Android 4\..{0,200}; {0,2}(M[12356789]|U[12368]|S[123])\ ?(pro)?(?: Build|\) AppleWebKit)' - device_replacement: 'Pipo $1$2' - brand_replacement: 'Pipo' - model_replacement: '$1$2' - - ######### - # Ployer - # @ref: http://en.ployer.cn/ - ######### + device_replacement: Philips $1 + brand_replacement: Philips + model_replacement: $1 + - regex: 'Android 4\..{0,200}; {0,2}(M[12356789]|U[12368]|S[123])\ ?(pro)?(?: Build|\) + AppleWebKit)' + device_replacement: Pipo $1$2 + brand_replacement: Pipo + model_replacement: $1$2 - regex: '; {0,2}(MOMO[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Ployer' - model_replacement: '$1' - - ######### - # Polaroid/ Acho - # @ref: http://polaroidstore.com/store/start.asp?category_id=382&category_id2=0&order=title&filter1=&filter2=&filter3=&view=all - ######### - - regex: '; {0,2}(?:Polaroid[ _]|)((?:MIDC\d{3,}|PMID\d{2,}|PTAB\d{3,})[^;/]{0,30}?)(\/[^;/]{0,30}|)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Polaroid' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Ployer + model_replacement: $1 + - regex: '; {0,2}(?:Polaroid[ _]|)((?:MIDC\d{3,}|PMID\d{2,}|PTAB\d{3,})[^;/]{0,30}?)(\/[^;/]{0,30}|)(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Polaroid + model_replacement: $1 - regex: '; {0,2}(?:Polaroid )(Tablet)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Polaroid' - model_replacement: '$1' - - ######### - # Pomp - # @ref: http://pompmobileshop.com/ - ######### - #~ TODO - - regex: '; {0,2}(POMP)[ _\-](.{1,200}?) {0,2}(?:Build|[;/\)])' - device_replacement: '$1 $2' - brand_replacement: 'Pomp' - model_replacement: '$2' - - ######### - # Positivo - # @ref: http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/ - ######### + device_replacement: $1 + brand_replacement: Polaroid + model_replacement: $1 + - regex: ; {0,2}(POMP)[ _\-](.{1,200}?) {0,2}(?:Build|[;/\)]) + device_replacement: $1 $2 + brand_replacement: Pomp + model_replacement: $2 - regex: '; {0,2}(TB07STA|TB10STA|TB07FTA|TB10FTA)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Positivo' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Positivo + model_replacement: $1 - regex: '; {0,2}(?:Positivo |)((?:YPY|Ypy)[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Positivo' - model_replacement: '$1' - - ######### - # POV - # @ref: http://www.pointofview-online.com/default2.php - # @TODO: Smartphone Models MOB-3515, MOB-5045-B missing - ######### + device_replacement: $1 + brand_replacement: Positivo + model_replacement: $1 - regex: '; {0,2}(MOB-[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'POV' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: POV + model_replacement: $1 - regex: '; {0,2}POV[ _\-]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'POV $1' - brand_replacement: 'POV' - model_replacement: '$1' - - regex: '; {0,2}((?:TAB-PLAYTAB|TAB-PROTAB|PROTAB|PlayTabPro|Mobii[ _\-]|TAB-P)[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: 'POV $1' - brand_replacement: 'POV' - model_replacement: '$1' - - ######### - # Prestigio - # @ref: http://www.prestigio.com/catalogue/MultiPhones - # @ref: http://www.prestigio.com/catalogue/MultiPads - ######### + device_replacement: POV $1 + brand_replacement: POV + model_replacement: $1 + - regex: '; {0,2}((?:TAB-PLAYTAB|TAB-PROTAB|PROTAB|PlayTabPro|Mobii[ _\-]|TAB-P)[^;/]*)(?: + Build|\) AppleWebKit)' + device_replacement: POV $1 + brand_replacement: POV + model_replacement: $1 - regex: '; {0,2}(?:Prestigio |)((?:PAP|PMP)\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Prestigio $1' - brand_replacement: 'Prestigio' - model_replacement: '$1' - - ######### - # Proscan - # @ref: http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr= - ######### + device_replacement: Prestigio $1 + brand_replacement: Prestigio + model_replacement: $1 - regex: '; {0,2}(PLT[0-9]{4}.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Proscan' - model_replacement: '$1' - - ######### - # QMobile - # @ref: http://www.qmobile.com.pk/ - ######### + device_replacement: $1 + brand_replacement: Proscan + model_replacement: $1 - regex: '; {0,2}(A2|A5|A8|A900)_?(Classic|)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Qmobile' - model_replacement: '$1 $2' + device_replacement: $1 $2 + brand_replacement: Qmobile + model_replacement: $1 $2 - regex: '; {0,2}(Q[Mm]obile)_([^_]+)_([^_]+?)(?: Build|\) AppleWebKit)' - device_replacement: 'Qmobile $2 $3' - brand_replacement: 'Qmobile' - model_replacement: '$2 $3' + device_replacement: Qmobile $2 $3 + brand_replacement: Qmobile + model_replacement: $2 $3 - regex: '; {0,2}(Q\-?[Mm]obile)[_ ](A[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Qmobile $2' - brand_replacement: 'Qmobile' - model_replacement: '$2' - - ######### - # Qmobilevn - # @ref: http://qmobile.vn/san-pham.html - ######### + device_replacement: Qmobile $2 + brand_replacement: Qmobile + model_replacement: $2 - regex: '; {0,2}(Q\-Smart)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Qmobilevn' - model_replacement: '$2' + device_replacement: $1 $2 + brand_replacement: Qmobilevn + model_replacement: $2 - regex: '; {0,2}(Q\-?[Mm]obile)[ _\-](S[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Qmobilevn' - model_replacement: '$2' - - ######### - # Quanta - # @ref: ? - ######### + device_replacement: $1 $2 + brand_replacement: Qmobilevn + model_replacement: $2 - regex: '; {0,2}(TA1013)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Quanta' - model_replacement: '$1' - - ######### - # RCA - # @ref: http://rcamobilephone.com/ - ######### + device_replacement: $1 + brand_replacement: Quanta + model_replacement: $1 - regex: '; (RCT\w+)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'RCA' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: RCA + model_replacement: $1 - regex: '; RCA (\w+)(?: Build|\) AppleWebKit)' - device_replacement: 'RCA $1' - brand_replacement: 'RCA' - model_replacement: '$1' - - ######### - # Rockchip - # @ref: http://www.rock-chips.com/a/cn/product/index.html - # @note: manufacturer sells chipsets - I assume that these UAs are dev-boards - ######### + device_replacement: RCA $1 + brand_replacement: RCA + model_replacement: $1 - regex: '; {0,2}(RK\d+),?(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Rockchip' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Rockchip + model_replacement: $1 - regex: ' Build/(RK\d+)' - device_replacement: '$1' - brand_replacement: 'Rockchip' - model_replacement: '$1' - - ######### - # Samsung Android Devices - # @ref: http://www.samsung.com/us/mobile/cell-phones/all-products - ######### - - regex: '; {0,2}(SAMSUNG |Samsung |)((?:Galaxy (?:Note II|S\d)|GT-I9082|GT-I9205|GT-N7\d{3}|SM-N9005)[^;/]{0,100})\/?[^;/]{0,50} Build/' - device_replacement: 'Samsung $1$2' - brand_replacement: 'Samsung' - model_replacement: '$2' + device_replacement: $1 + brand_replacement: Rockchip + model_replacement: $1 + - regex: ; {0,2}(SAMSUNG |Samsung |)((?:Galaxy (?:Note II|S\d)|GT-I9082|GT-I9205|GT-N7\d{3}|SM-N9005)[^;/]{0,100})\/?[^;/]{0,50} + Build/ + device_replacement: Samsung $1$2 + brand_replacement: Samsung + model_replacement: $2 - regex: '; {0,2}(Google |)(Nexus [Ss](?: 4G|)) Build/' - device_replacement: 'Samsung $1$2' - brand_replacement: 'Samsung' - model_replacement: '$2' - - regex: '; {0,2}(SAMSUNG |Samsung )([^\/]{0,50})\/[^ ]{0,50} Build/' - device_replacement: 'Samsung $2' - brand_replacement: 'Samsung' - model_replacement: '$2' - - regex: '; {0,2}(Galaxy(?: Ace| Nexus| S ?II+|Nexus S| with MCR 1.2| Mini Plus 4G|)) Build/' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - regex: '; {0,2}(SAMSUNG[ _\-]|)(?:SAMSUNG[ _\-])([^;/]{1,100}) Build' - device_replacement: 'Samsung $2' - brand_replacement: 'Samsung' - model_replacement: '$2' - - regex: '; {0,2}(SAMSUNG-|)(GT\-[BINPS]\d{4}[^\/]{0,50})(\/[^ ]{0,50}) Build' - device_replacement: 'Samsung $1$2$3' - brand_replacement: 'Samsung' - model_replacement: '$2' - - regex: '(?:; {0,2}|^)((?:GT\-[BIiNPS]\d{4}|I9\d{2}0[A-Za-z\+]?\b)[^;/\)]*?)(?:Build|Linux|MIUI|[;/\)])' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - regex: '; (SAMSUNG-)([A-Za-z0-9\-]{0,50}).{0,200} Build/' - device_replacement: 'Samsung $1$2' - brand_replacement: 'Samsung' - model_replacement: '$2' - - regex: '; {0,2}((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\-[A-Za-z0-9 ]{1,50})(/?[^ ]*|) Build' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - regex: '; {0,2}((?:SC)\-[A-Za-z0-9 ]{1,50})(/?[^ ]*|)\)' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' + device_replacement: Samsung $1$2 + brand_replacement: Samsung + model_replacement: $2 + - regex: ; {0,2}(SAMSUNG |Samsung )([^\/]{0,50})\/[^ ]{0,50} Build/ + device_replacement: Samsung $2 + brand_replacement: Samsung + model_replacement: $2 + - regex: '; {0,2}(Galaxy(?: Ace| Nexus| S ?II+|Nexus S| with MCR 1.2| Mini Plus + 4G|)) Build/' + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: ; {0,2}(SAMSUNG[ _\-]|)(?:SAMSUNG[ _\-])([^;/]{1,100}) Build + device_replacement: Samsung $2 + brand_replacement: Samsung + model_replacement: $2 + - regex: ; {0,2}(SAMSUNG-|)(GT\-[BINPS]\d{4}[^\/]{0,50})(\/[^ ]{0,50}) Build + device_replacement: Samsung $1$2$3 + brand_replacement: Samsung + model_replacement: $2 + - regex: (?:; {0,2}|^)((?:GT\-[BIiNPS]\d{4}|I9\d{2}0[A-Za-z\+]?\b)[^;/\)]*?)(?:Build|Linux|MIUI|[;/\)]) + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: ; (SAMSUNG-)([A-Za-z0-9\-]{0,50}).{0,200} Build/ + device_replacement: Samsung $1$2 + brand_replacement: Samsung + model_replacement: $2 + - regex: ; {0,2}((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\-[A-Za-z0-9 ]{1,50})(/?[^ ]*|) Build + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: ; {0,2}((?:SC)\-[A-Za-z0-9 ]{1,50})(/?[^ ]*|)\) + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 - regex: ' ((?:SCH)\-[A-Za-z0-9 ]{1,50})(/?[^ ]*|) Build' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - regex: '; {0,2}(Behold ?(?:2|II)|YP\-G[^;/]{1,100}|EK-GC100|SCL21|I9300) Build' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - regex: '; {0,2}((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\-[A-Za-z0-9]{5,6})[\)]' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - ######### - # Sharp - # @ref: http://www.sharp-phone.com/en/index.html - # @ref: http://www.android.com/devices/?country=all&m=sharp - ######### + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: ; {0,2}(Behold ?(?:2|II)|YP\-G[^;/]{1,100}|EK-GC100|SCL21|I9300) Build + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: ; {0,2}((?:SCH|SGH|SHV|SHW|SPH|SC|SM)\-[A-Za-z0-9]{5,6})[\)] + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 - regex: '; {0,2}(SH\-?\d\d[^;/]{1,100}|SBM\d[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Sharp' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Sharp + model_replacement: $1 - regex: '; {0,2}(SHARP[ -])([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Sharp' - model_replacement: '$2' - - ######### - # Simvalley - # @ref: http://www.simvalley-mobile.de/ - ######### + device_replacement: $1$2 + brand_replacement: Sharp + model_replacement: $2 - regex: '; {0,2}(SPX[_\-]\d[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Simvalley' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Simvalley + model_replacement: $1 - regex: '; {0,2}(SX7\-PEARL\.GmbH)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Simvalley' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Simvalley + model_replacement: $1 - regex: '; {0,2}(SP[T]?\-\d{2}[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Simvalley' - model_replacement: '$1' - - ######### - # SK Telesys - # @ref: http://www.sk-w.com/phone/phone_list.jsp - # @ref: http://www.android.com/devices/?country=all&m=sk-telesys - ######### + device_replacement: $1 + brand_replacement: Simvalley + model_replacement: $1 - regex: '; {0,2}(SK\-.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'SKtelesys' - model_replacement: '$1' - - ######### - # Skytex - # @ref: http://skytex.com/android - ######### + device_replacement: $1 + brand_replacement: SKtelesys + model_replacement: $1 - regex: '; {0,2}(?:SKYTEX|SX)-([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Skytex' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Skytex + model_replacement: $1 - regex: '; {0,2}(IMAGINE [^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Skytex' - model_replacement: '$1' - - ######### - # SmartQ - # @ref: http://en.smartdevices.com.cn/Products/ - # @models: Z8, X7, U7H, U7, T30, T20, Ten3, V5-II, T7-3G, SmartQ5, K7, S7, Q8, T19, Ten2, Ten, R10, T7, R7, V5, V7, SmartQ7 - ######### + device_replacement: $1 + brand_replacement: Skytex + model_replacement: $1 - regex: '; {0,2}(SmartQ) ?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ######### - # Smartbitt - # @ref: http://www.smartbitt.com/ - # @missing: SBT Useragents - ######### + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}(WF7C|WF10C|SBT[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Smartbitt' - model_replacement: '$1' - - ######### - # Softbank (Operator Branded Devices) - # @ref: http://www.ipentec.com/document/document.aspx?page=android-useragent - ######### - - regex: '; {0,2}(SBM(?:003SH|005SH|006SH|007SH|102SH)) Build' - device_replacement: '$1' - brand_replacement: 'Sharp' - model_replacement: '$1' - - regex: '; {0,2}(003P|101P|101P11C|102P) Build' - device_replacement: '$1' - brand_replacement: 'Panasonic' - model_replacement: '$1' - - regex: '; {0,2}(00\dZ) Build/' - device_replacement: '$1' - brand_replacement: 'ZTE' - model_replacement: '$1' - - regex: '; HTC(X06HT) Build' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: '$1' - - regex: '; {0,2}(001HT|X06HT) Build' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: '$1' - - regex: '; {0,2}(201M) Build' - device_replacement: '$1' - brand_replacement: 'Motorola' - model_replacement: 'XT902' - - ######### - # Trekstor - # @ref: http://www.trekstor.co.uk/surftabs-en.html - # @note: Must come before SonyEricsson - ######### - - regex: '; {0,2}(ST\d{4}.{0,200})Build/ST' - device_replacement: 'Trekstor $1' - brand_replacement: 'Trekstor' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Smartbitt + model_replacement: $1 + - regex: ; {0,2}(SBM(?:003SH|005SH|006SH|007SH|102SH)) Build + device_replacement: $1 + brand_replacement: Sharp + model_replacement: $1 + - regex: ; {0,2}(003P|101P|101P11C|102P) Build + device_replacement: $1 + brand_replacement: Panasonic + model_replacement: $1 + - regex: ; {0,2}(00\dZ) Build/ + device_replacement: $1 + brand_replacement: ZTE + model_replacement: $1 + - regex: ; HTC(X06HT) Build + device_replacement: $1 + brand_replacement: HTC + model_replacement: $1 + - regex: ; {0,2}(001HT|X06HT) Build + device_replacement: $1 + brand_replacement: HTC + model_replacement: $1 + - regex: ; {0,2}(201M) Build + device_replacement: $1 + brand_replacement: Motorola + model_replacement: XT902 + - regex: ; {0,2}(ST\d{4}.{0,200})Build/ST + device_replacement: Trekstor $1 + brand_replacement: Trekstor + model_replacement: $1 - regex: '; {0,2}(ST\d{4}.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Trekstor $1' - brand_replacement: 'Trekstor' - model_replacement: '$1' - - ######### - # SonyEricsson - # @note: Must come before nokia since they also use symbian - # @ref: http://www.android.com/devices/?country=all&m=sony-ericssons - # @TODO: type! - ######### - # android matchers - - regex: '; {0,2}(Sony ?Ericsson ?)([^;/]{1,100}) Build' - device_replacement: '$1$2' - brand_replacement: 'SonyEricsson' - model_replacement: '$2' - - regex: '; {0,2}((?:SK|ST|E|X|LT|MK|MT|WT)\d{2}[a-z0-9]*(?:-o|)|R800i|U20i) Build' - device_replacement: '$1' - brand_replacement: 'SonyEricsson' - model_replacement: '$1' - # TODO X\d+ is wrong - - regex: '; {0,2}(Xperia (?:A8|Arc|Acro|Active|Live with Walkman|Mini|Neo|Play|Pro|Ray|X\d+)[^;/]{0,50}) Build' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'SonyEricsson' - model_replacement: '$1' - - ######### - # Sony - # @ref: http://www.sonymobile.co.jp/index.html - # @ref: http://www.sonymobile.com/global-en/products/phones/ - # @ref: http://www.sony.jp/tablet/ - ######### + device_replacement: Trekstor $1 + brand_replacement: Trekstor + model_replacement: $1 + - regex: ; {0,2}(Sony ?Ericsson ?)([^;/]{1,100}) Build + device_replacement: $1$2 + brand_replacement: SonyEricsson + model_replacement: $2 + - regex: ; {0,2}((?:SK|ST|E|X|LT|MK|MT|WT)\d{2}[a-z0-9]*(?:-o|)|R800i|U20i) Build + device_replacement: $1 + brand_replacement: SonyEricsson + model_replacement: $1 + - regex: ; {0,2}(Xperia (?:A8|Arc|Acro|Active|Live with Walkman|Mini|Neo|Play|Pro|Ray|X\d+)[^;/]{0,50}) + Build + regex_flag: i + device_replacement: $1 + brand_replacement: SonyEricsson + model_replacement: $1 - regex: '; Sony (Tablet[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Sony $1' - brand_replacement: 'Sony' - model_replacement: '$1' + device_replacement: Sony $1 + brand_replacement: Sony + model_replacement: $1 - regex: '; Sony ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Sony $1' - brand_replacement: 'Sony' - model_replacement: '$1' + device_replacement: Sony $1 + brand_replacement: Sony + model_replacement: $1 - regex: '; {0,2}(Sony)([A-Za-z0-9\-]+)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}(Xperia [^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Sony' - model_replacement: '$1' - - regex: '; {0,2}(C(?:1[0-9]|2[0-9]|53|55|6[0-9])[0-9]{2}|D[25]\d{3}|D6[56]\d{2})(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Sony' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Sony + model_replacement: $1 + - regex: '; {0,2}(C(?:1[0-9]|2[0-9]|53|55|6[0-9])[0-9]{2}|D[25]\d{3}|D6[56]\d{2})(?: + Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: Sony + model_replacement: $1 - regex: '; {0,2}(SGP\d{3}|SGPT\d{2})(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Sony' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Sony + model_replacement: $1 - regex: '; {0,2}(NW-Z1000Series)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Sony' - model_replacement: '$1' - - ########## - # Sony PlayStation - # @ref: http://playstation.com - # The Vita spoofs the Kindle - ########## - - regex: 'PLAYSTATION 3' - device_replacement: 'PlayStation 3' - brand_replacement: 'Sony' - model_replacement: 'PlayStation 3' - - regex: '(PlayStation (?:Portable|Vita|\d+))' - device_replacement: '$1' - brand_replacement: 'Sony' - model_replacement: '$1' - - ######### - # Spice - # @ref: http://www.spicemobilephones.co.in/ - ######### - - regex: '; {0,2}((?:CSL_Spice|Spice|SPICE|CSL)[ _\-]?|)([Mm][Ii])([ _\-]|)(\d{3}[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2$3$4' - brand_replacement: 'Spice' - model_replacement: 'Mi$4' - - ######### - # Sprint (Operator Branded Devices) - # @ref: - ######### - - regex: '; {0,2}(Sprint )(.{1,200}?) {0,2}(?:Build|[;/])' - device_replacement: '$1$2' - brand_replacement: 'Sprint' - model_replacement: '$2' + device_replacement: $1 + brand_replacement: Sony + model_replacement: $1 + - regex: PLAYSTATION 3 + device_replacement: PlayStation 3 + brand_replacement: Sony + model_replacement: PlayStation 3 + - regex: (PlayStation (?:Portable|Vita|\d+)) + device_replacement: $1 + brand_replacement: Sony + model_replacement: $1 + - regex: '; {0,2}((?:CSL_Spice|Spice|SPICE|CSL)[ _\-]?|)([Mm][Ii])([ _\-]|)(\d{3}[^;/]*)(?: + Build|\) AppleWebKit)' + device_replacement: $1$2$3$4 + brand_replacement: Spice + model_replacement: Mi$4 + - regex: ; {0,2}(Sprint )(.{1,200}?) {0,2}(?:Build|[;/]) + device_replacement: $1$2 + brand_replacement: Sprint + model_replacement: $2 - regex: '\b(Sprint)[: ]([^;,/ ]+)' - device_replacement: '$1$2' - brand_replacement: 'Sprint' - model_replacement: '$2' - - ######### - # Tagi - # @ref: ?? - ######### + device_replacement: $1$2 + brand_replacement: Sprint + model_replacement: $2 - regex: '; {0,2}(TAGI[ ]?)(MID) ?([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2$3' - brand_replacement: 'Tagi' - model_replacement: '$2$3' - - ######### - # Tecmobile - # @ref: http://www.tecmobile.com/ - ######### + device_replacement: $1$2$3 + brand_replacement: Tagi + model_replacement: $2$3 - regex: '; {0,2}(Oyster500|Opal 800)(?: Build|\) AppleWebKit)' - device_replacement: 'Tecmobile $1' - brand_replacement: 'Tecmobile' - model_replacement: '$1' - - ######### - # Tecno - # @ref: www.tecno-mobile.com/‎ - ######### + device_replacement: Tecmobile $1 + brand_replacement: Tecmobile + model_replacement: $1 - regex: '; {0,2}(TECNO[ _])([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Tecno' - model_replacement: '$2' - - ######### - # Telechips, Techvision evaluation boards - # @ref: - ######### + device_replacement: $1$2 + brand_replacement: Tecno + model_replacement: $2 - regex: '; {0,2}Android for (Telechips|Techvision) ([^ ]+) ' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ######### - # Telstra - # @ref: http://www.telstra.com.au/home-phone/thub-2/ - # @ref: https://support.google.com/googleplay/answer/1727131?hl=en - ######### + regex_flag: i + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}(T-Hub2)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Telstra' - model_replacement: '$1' - - ######### - # Terra - # @ref: http://www.wortmann.de/ - ######### + device_replacement: $1 + brand_replacement: Telstra + model_replacement: $1 - regex: '; {0,2}(PAD) ?(100[12])(?: Build|\) AppleWebKit)' - device_replacement: 'Terra $1$2' - brand_replacement: 'Terra' - model_replacement: '$1$2' - - ######### - # Texet - # @ref: http://www.texet.ru/tablet/ - ######### + device_replacement: Terra $1$2 + brand_replacement: Terra + model_replacement: $1$2 - regex: '; {0,2}(T[BM]-\d{3}[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Texet' - model_replacement: '$1' - - ######### - # Thalia - # @ref: http://www.thalia.de/shop/tolino-shine-ereader/show/ - ######### + device_replacement: $1 + brand_replacement: Texet + model_replacement: $1 - regex: '; {0,2}(tolino [^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Thalia' - model_replacement: '$1' - - regex: '; {0,2}Build/.{0,200} (TOLINO_BROWSER)' - device_replacement: '$1' - brand_replacement: 'Thalia' - model_replacement: 'Tolino Shine' - - ######### - # Thl - # @ref: http://en.thl.com.cn/Mobile - # @ref: http://thlmobilestore.com - ######### + device_replacement: $1 + brand_replacement: Thalia + model_replacement: $1 + - regex: ; {0,2}Build/.{0,200} (TOLINO_BROWSER) + device_replacement: $1 + brand_replacement: Thalia + model_replacement: Tolino Shine - regex: '; {0,2}(?:CJ[ -])?(ThL|THL)[ -]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Thl' - model_replacement: '$2' + device_replacement: $1 $2 + brand_replacement: Thl + model_replacement: $2 - regex: '; {0,2}(T100|T200|T5|W100|W200|W8s)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Thl' - model_replacement: '$1' - - ######### - # T-Mobile (Operator Branded Devices) - ######### - # @ref: https://en.wikipedia.org/wiki/HTC_Hero - - regex: '; {0,2}(T-Mobile[ _]G2[ _]Touch) Build' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: 'Hero' - # @ref: https://en.wikipedia.org/wiki/HTC_Desire_Z - - regex: '; {0,2}(T-Mobile[ _]G2) Build' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: 'Desire Z' - - regex: '; {0,2}(T-Mobile myTouch Q) Build' - device_replacement: '$1' - brand_replacement: 'Huawei' - model_replacement: 'U8730' - - regex: '; {0,2}(T-Mobile myTouch) Build' - device_replacement: '$1' - brand_replacement: 'Huawei' - model_replacement: 'U8680' - - regex: '; {0,2}(T-Mobile_Espresso) Build' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: 'Espresso' - - regex: '; {0,2}(T-Mobile G1) Build' - device_replacement: '$1' - brand_replacement: 'HTC' - model_replacement: 'Dream' - - regex: '\b(T-Mobile ?|)(myTouch)[ _]?([34]G)[ _]?([^\/]*) (?:Mozilla|Build)' - device_replacement: '$1$2 $3 $4' - brand_replacement: 'HTC' - model_replacement: '$2 $3 $4' - - regex: '\b(T-Mobile)_([^_]+)_(.{0,200}) Build' - device_replacement: '$1 $2 $3' - brand_replacement: 'Tmobile' - model_replacement: '$2 $3' - - regex: '\b(T-Mobile)[_ ]?(.{0,200}?)Build' - device_replacement: '$1 $2' - brand_replacement: 'Tmobile' - model_replacement: '$2' - - ######### - # Tomtec - # @ref: http://www.tom-tec.eu/pages/tablets.php - ######### + device_replacement: $1 + brand_replacement: Thl + model_replacement: $1 + - regex: ; {0,2}(T-Mobile[ _]G2[ _]Touch) Build + device_replacement: $1 + brand_replacement: HTC + model_replacement: Hero + - regex: ; {0,2}(T-Mobile[ _]G2) Build + device_replacement: $1 + brand_replacement: HTC + model_replacement: Desire Z + - regex: ; {0,2}(T-Mobile myTouch Q) Build + device_replacement: $1 + brand_replacement: Huawei + model_replacement: U8730 + - regex: ; {0,2}(T-Mobile myTouch) Build + device_replacement: $1 + brand_replacement: Huawei + model_replacement: U8680 + - regex: ; {0,2}(T-Mobile_Espresso) Build + device_replacement: $1 + brand_replacement: HTC + model_replacement: Espresso + - regex: ; {0,2}(T-Mobile G1) Build + device_replacement: $1 + brand_replacement: HTC + model_replacement: Dream + - regex: \b(T-Mobile ?|)(myTouch)[ _]?([34]G)[ _]?([^\/]*) (?:Mozilla|Build) + device_replacement: $1$2 $3 $4 + brand_replacement: HTC + model_replacement: $2 $3 $4 + - regex: \b(T-Mobile)_([^_]+)_(.{0,200}) Build + device_replacement: $1 $2 $3 + brand_replacement: Tmobile + model_replacement: $2 $3 + - regex: \b(T-Mobile)[_ ]?(.{0,200}?)Build + device_replacement: $1 $2 + brand_replacement: Tmobile + model_replacement: $2 - regex: ' (ATP[0-9]{4})(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Tomtec' - model_replacement: '$1' - - ######### - # Tooky - # @ref: http://www.tookymobile.com/ - ######### + device_replacement: $1 + brand_replacement: Tomtec + model_replacement: $1 - regex: ' ?(TOOKY)[ _\-]([^;/]{1,100}) ?(?:Build|;)' - regex_flag: 'i' - device_replacement: '$1 $2' - brand_replacement: 'Tooky' - model_replacement: '$2' - - ######### - # Toshiba - # @ref: http://www.toshiba.co.jp/ - # @missing: LT170, Thrive 7, TOSHIBA STB10 - ######### - - regex: '\b(TOSHIBA_AC_AND_AZ|TOSHIBA_FOLIO_AND_A|FOLIO_AND_A)' - device_replacement: '$1' - brand_replacement: 'Toshiba' - model_replacement: 'Folio 100' + regex_flag: i + device_replacement: $1 $2 + brand_replacement: Tooky + model_replacement: $2 + - regex: \b(TOSHIBA_AC_AND_AZ|TOSHIBA_FOLIO_AND_A|FOLIO_AND_A) + device_replacement: $1 + brand_replacement: Toshiba + model_replacement: Folio 100 - regex: '; {0,2}([Ff]olio ?100)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Toshiba' - model_replacement: 'Folio 100' - - regex: '; {0,2}(AT[0-9]{2,3}(?:\-A|LE\-A|PE\-A|SE|a|)|AT7-A|AT1S0|Hikari-iFrame/WDPF-[^;/]{1,100}|THRiVE|Thrive)(?: Build|\) AppleWebKit)' - device_replacement: 'Toshiba $1' - brand_replacement: 'Toshiba' - model_replacement: '$1' - - ######### - # Touchmate - # @ref: http://touchmatepc.com/new/ - ######### + device_replacement: $1 + brand_replacement: Toshiba + model_replacement: Folio 100 + - regex: '; {0,2}(AT[0-9]{2,3}(?:\-A|LE\-A|PE\-A|SE|a|)|AT7-A|AT1S0|Hikari-iFrame/WDPF-[^;/]{1,100}|THRiVE|Thrive)(?: + Build|\) AppleWebKit)' + device_replacement: Toshiba $1 + brand_replacement: Toshiba + model_replacement: $1 - regex: '; {0,2}(TM-MID\d+[^;/]{1,50}|TOUCHMATE|MID-750)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Touchmate' - model_replacement: '$1' - # @todo: needs verification user-agents missing + device_replacement: $1 + brand_replacement: Touchmate + model_replacement: $1 - regex: '; {0,2}(TM-SM\d+[^;/]{1,50}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Touchmate' - model_replacement: '$1' - - ######### - # Treq - # @ref: http://www.treq.co.id/product - ######### + device_replacement: $1 + brand_replacement: Touchmate + model_replacement: $1 - regex: '; {0,2}(A10 [Bb]asic2?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Treq' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Treq + model_replacement: $1 - regex: '; {0,2}(TREQ[ _\-])([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1$2' - brand_replacement: 'Treq' - model_replacement: '$2' - - ######### - # Umeox - # @ref: http://umeox.com/ - # @models: A936|A603|X-5|X-3 - ######### - # @todo: guessed markers + regex_flag: i + device_replacement: $1$2 + brand_replacement: Treq + model_replacement: $2 - regex: '; {0,2}(X-?5|X-?3)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Umeox' - model_replacement: '$1' - # @todo: guessed markers + device_replacement: $1 + brand_replacement: Umeox + model_replacement: $1 - regex: '; {0,2}(A502\+?|A936|A603|X1|X2)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Umeox' - model_replacement: '$1' - - ######### - # Vernee - # @ref: http://vernee.cc/ - # @models: Thor - Thor E - ######### - - regex: '; thor Build/' - device_replacement: 'Thor' - brand_replacement: 'Vernee' - model_replacement: 'Thor' - # Regex to modidy for Thor Plus (don't find example UA) - - regex: '; Thor (E)? Build/' - device_replacement: 'Thor $1' - brand_replacement: 'Vernee' - model_replacement: 'Thor' - - regex: '; Apollo Lite Build/' - device_replacement: 'Apollo Lite' - brand_replacement: 'Vernee' - model_replacement: 'Apollo' - - ######### - # Versus - # @ref: http://versusuk.com/support.html - ######### + device_replacement: $1 + brand_replacement: Umeox + model_replacement: $1 + - regex: ; thor Build/ + device_replacement: Thor + brand_replacement: Vernee + model_replacement: Thor + - regex: ; Thor (E)? Build/ + device_replacement: Thor $1 + brand_replacement: Vernee + model_replacement: Thor + - regex: ; Apollo Lite Build/ + device_replacement: Apollo Lite + brand_replacement: Vernee + model_replacement: Apollo - regex: '(TOUCH(?:TAB|PAD).{1,200}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Versus $1' - brand_replacement: 'Versus' - model_replacement: '$1' - - ######### - # Vertu - # @ref: http://www.vertu.com/ - ######### + regex_flag: i + device_replacement: Versus $1 + brand_replacement: Versus + model_replacement: $1 - regex: '(VERTU) ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'Vertu' - model_replacement: '$2' - - ######### - # Videocon - # @ref: http://www.videoconmobiles.com - ######### - - regex: '; {0,2}(Videocon)[ _\-]([^;/]{1,100}?) {0,2}(?:Build|;)' - device_replacement: '$1 $2' - brand_replacement: 'Videocon' - model_replacement: '$2' + device_replacement: $1 $2 + brand_replacement: Vertu + model_replacement: $2 + - regex: ; {0,2}(Videocon)[ _\-]([^;/]{1,100}?) {0,2}(?:Build|;) + device_replacement: $1 $2 + brand_replacement: Videocon + model_replacement: $2 - regex: ' (VT\d{2}[A-Za-z]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Videocon' - model_replacement: '$1' - - ######### - # Viewsonic - # @ref: http://viewsonic.com - ######### + device_replacement: $1 + brand_replacement: Videocon + model_replacement: $1 - regex: '; {0,2}((?:ViewPad|ViewPhone|VSD)[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Viewsonic' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Viewsonic + model_replacement: $1 - regex: '; {0,2}(ViewSonic-)([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'Viewsonic' - model_replacement: '$2' + device_replacement: $1$2 + brand_replacement: Viewsonic + model_replacement: $2 - regex: '; {0,2}(GTablet.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Viewsonic' - model_replacement: '$1' - - ######### - # vivo - # @ref: http://vivo.cn/ - ######### + device_replacement: $1 + brand_replacement: Viewsonic + model_replacement: $1 - regex: '; {0,2}([Vv]ivo)[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'vivo' - model_replacement: '$2' - - ######### - # Vodafone (Operator Branded Devices) - # @ref: ?? - ######### + device_replacement: $1 $2 + brand_replacement: vivo + model_replacement: $2 - regex: '(Vodafone) (.{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ######### - # Walton - # @ref: http://www.waltonbd.com/ - ######### + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 - regex: '; {0,2}(?:Walton[ _\-]|)(Primo[ _\-][^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Walton $1' - brand_replacement: 'Walton' - model_replacement: '$1' - - ######### - # Wiko - # @ref: http://fr.wikomobile.com/collection.php?s=Smartphones - ######### - - regex: '; {0,2}(?:WIKO[ \-]|)(CINK\+?|BARRY|BLOOM|DARKFULL|DARKMOON|DARKNIGHT|DARKSIDE|FIZZ|HIGHWAY|IGGY|OZZY|RAINBOW|STAIRWAY|SUBLIM|WAX|CINK [^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Wiko $1' - brand_replacement: 'Wiko' - model_replacement: '$1' - - ######### - # WellcoM - # @ref: ?? - ######### + regex_flag: i + device_replacement: Walton $1 + brand_replacement: Walton + model_replacement: $1 + - regex: '; {0,2}(?:WIKO[ \-]|)(CINK\+?|BARRY|BLOOM|DARKFULL|DARKMOON|DARKNIGHT|DARKSIDE|FIZZ|HIGHWAY|IGGY|OZZY|RAINBOW|STAIRWAY|SUBLIM|WAX|CINK + [^;/]{1,100}?)(?: Build|\) AppleWebKit)' + regex_flag: i + device_replacement: Wiko $1 + brand_replacement: Wiko + model_replacement: $1 - regex: '; {0,2}WellcoM-([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Wellcom $1' - brand_replacement: 'Wellcom' - model_replacement: '$1' - - ########## - # WeTab - # @ref: http://wetab.mobi/ - ########## - - regex: '(?:(WeTab)-Browser|; (wetab) Build)' - device_replacement: '$1' - brand_replacement: 'WeTab' - model_replacement: 'WeTab' - - ######### - # Wolfgang - # @ref: http://wolfgangmobile.com/ - ######### + device_replacement: Wellcom $1 + brand_replacement: Wellcom + model_replacement: $1 + - regex: (?:(WeTab)-Browser|; (wetab) Build) + device_replacement: $1 + brand_replacement: WeTab + model_replacement: WeTab - regex: '; {0,2}(AT-AS[^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Wolfgang $1' - brand_replacement: 'Wolfgang' - model_replacement: '$1' - - ######### - # Woxter - # @ref: http://www.woxter.es/es-es/categories/index - ######### + device_replacement: Wolfgang $1 + brand_replacement: Wolfgang + model_replacement: $1 - regex: '; {0,2}(?:Woxter|Wxt) ([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'Woxter $1' - brand_replacement: 'Woxter' - model_replacement: '$1' - - ######### - # Yarvik Zania - # @ref: http://yarvik.com - ######### - - regex: '; {0,2}(?:Xenta |Luna |)(TAB[234][0-9]{2}|TAB0[78]-\d{3}|TAB0?9-\d{3}|TAB1[03]-\d{3}|SMP\d{2}-\d{3})(?: Build|\) AppleWebKit)' - device_replacement: 'Yarvik $1' - brand_replacement: 'Yarvik' - model_replacement: '$1' - - ######### - # Yifang - # @note: Needs to be at the very last as manufacturer builds for other brands. - # @ref: http://www.yifangdigital.com/ - # @models: M1010, M1011, M1007, M1008, M1005, M899, M899LP, M909, M8000, - # M8001, M8002, M8003, M849, M815, M816, M819, M805, M878, M780LPW, - # M778, M7000, M7000AD, M7000NBD, M7001, M7002, M7002KBD, M777, M767, - # M789, M799, M769, M757, M755, M753, M752, M739, M729, M723, M712, M727 - ######### + device_replacement: Woxter $1 + brand_replacement: Woxter + model_replacement: $1 + - regex: '; {0,2}(?:Xenta |Luna |)(TAB[234][0-9]{2}|TAB0[78]-\d{3}|TAB0?9-\d{3}|TAB1[03]-\d{3}|SMP\d{2}-\d{3})(?: + Build|\) AppleWebKit)' + device_replacement: Yarvik $1 + brand_replacement: Yarvik + model_replacement: $1 - regex: '; {0,2}([A-Z]{2,4})(M\d{3,}[A-Z]{2})([^;\)\/]*)(?: Build|[;\)])' - device_replacement: 'Yifang $1$2$3' - brand_replacement: 'Yifang' - model_replacement: '$2' - - ######### - # XiaoMi - # @ref: http://www.xiaomi.com/event/buyphone - ######### - - regex: '; {0,2}((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/]*) (Build|MIUI)/' - device_replacement: 'XiaoMi $1' - brand_replacement: 'XiaoMi' - model_replacement: '$1' - - regex: '; {0,2}((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/\)]*)' - device_replacement: 'XiaoMi $1' - brand_replacement: 'XiaoMi' - model_replacement: '$1' - - regex: '; {0,2}(MIX) (Build|MIUI)/' - device_replacement: 'XiaoMi $1' - brand_replacement: 'XiaoMi' - model_replacement: '$1' - - regex: '; {0,2}((MIX) ([^;/]*)) (Build|MIUI)/' - device_replacement: 'XiaoMi $1' - brand_replacement: 'XiaoMi' - model_replacement: '$1' - - ######### - # Xolo - # @ref: http://www.xolo.in/ - ######### + device_replacement: Yifang $1$2$3 + brand_replacement: Yifang + model_replacement: $2 + - regex: ; {0,2}((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/]*) (Build|MIUI)/ + device_replacement: XiaoMi $1 + brand_replacement: XiaoMi + model_replacement: $1 + - regex: ; {0,2}((Mi|MI|HM|MI-ONE|Redmi)[ -](NOTE |Note |)[^;/\)]*) + device_replacement: XiaoMi $1 + brand_replacement: XiaoMi + model_replacement: $1 + - regex: ; {0,2}(MIX) (Build|MIUI)/ + device_replacement: XiaoMi $1 + brand_replacement: XiaoMi + model_replacement: $1 + - regex: ; {0,2}((MIX) ([^;/]*)) (Build|MIUI)/ + device_replacement: XiaoMi $1 + brand_replacement: XiaoMi + model_replacement: $1 - regex: '; {0,2}XOLO[ _]([^;/]{0,30}tab.{0,30})(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Xolo $1' - brand_replacement: 'Xolo' - model_replacement: '$1' + regex_flag: i + device_replacement: Xolo $1 + brand_replacement: Xolo + model_replacement: $1 - regex: '; {0,2}XOLO[ _]([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Xolo $1' - brand_replacement: 'Xolo' - model_replacement: '$1' + regex_flag: i + device_replacement: Xolo $1 + brand_replacement: Xolo + model_replacement: $1 - regex: '; {0,2}(q\d0{2,3}[a-z]?)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: 'Xolo $1' - brand_replacement: 'Xolo' - model_replacement: '$1' - - ######### - # Xoro - # @ref: http://www.xoro.de/produkte/ - ######### + regex_flag: i + device_replacement: Xolo $1 + brand_replacement: Xolo + model_replacement: $1 - regex: '; {0,2}(PAD ?[79]\d+[^;/]{0,50}|TelePAD\d+[^;/])(?: Build|\) AppleWebKit)' - device_replacement: 'Xoro $1' - brand_replacement: 'Xoro' - model_replacement: '$1' - - ######### - # Zopo - # @ref: http://www.zopomobiles.com/products.html - ######### - - regex: '; {0,2}(?:(?:ZOPO|Zopo)[ _]([^;/]{1,100}?)|(ZP ?(?:\d{2}[^;/]{1,100}|C2))|(C[2379]))(?: Build|\) AppleWebKit)' - device_replacement: '$1$2$3' - brand_replacement: 'Zopo' - model_replacement: '$1$2$3' - - ######### - # ZiiLabs - # @ref: http://www.ziilabs.com/products/platforms/androidreferencetablets.php - ######### + device_replacement: Xoro $1 + brand_replacement: Xoro + model_replacement: $1 + - regex: '; {0,2}(?:(?:ZOPO|Zopo)[ _]([^;/]{1,100}?)|(ZP ?(?:\d{2}[^;/]{1,100}|C2))|(C[2379]))(?: + Build|\) AppleWebKit)' + device_replacement: $1$2$3 + brand_replacement: Zopo + model_replacement: $1$2$3 - regex: '; {0,2}(ZiiLABS) (Zii[^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'ZiiLabs' - model_replacement: '$2' + device_replacement: $1 $2 + brand_replacement: ZiiLabs + model_replacement: $2 - regex: '; {0,2}(Zii)_([^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'ZiiLabs' - model_replacement: '$2' - - ######### - # ZTE - # @ref: http://www.ztedevices.com/ - ######### - - regex: '; {0,2}(ARIZONA|(?:ATLAS|Atlas) W|D930|Grand (?:[SX][^;]{0,200}?|Era|Memo[^;]{0,200}?)|JOE|(?:Kis|KIS)\b[^;]{0,200}?|Libra|Light [^;]{0,200}?|N8[056][01]|N850L|N8000|N9[15]\d{2}|N9810|NX501|Optik|(?:Vip )Racer[^;]{0,200}?|RacerII|RACERII|San Francisco[^;]{0,200}?|V9[AC]|V55|V881|Z[679][0-9]{2}[A-z]?)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'ZTE' - model_replacement: '$1' + device_replacement: $1 $2 + brand_replacement: ZiiLabs + model_replacement: $2 + - regex: '; {0,2}(ARIZONA|(?:ATLAS|Atlas) W|D930|Grand (?:[SX][^;]{0,200}?|Era|Memo[^;]{0,200}?)|JOE|(?:Kis|KIS)\b[^;]{0,200}?|Libra|Light + [^;]{0,200}?|N8[056][01]|N850L|N8000|N9[15]\d{2}|N9810|NX501|Optik|(?:Vip )Racer[^;]{0,200}?|RacerII|RACERII|San + Francisco[^;]{0,200}?|V9[AC]|V55|V881|Z[679][0-9]{2}[A-z]?)(?: Build|\) AppleWebKit)' + device_replacement: $1 + brand_replacement: ZTE + model_replacement: $1 - regex: '; {0,2}([A-Z]\d+)_USA_[^;]{0,200}(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'ZTE' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: ZTE + model_replacement: $1 - regex: '; {0,2}(SmartTab\d+)[^;]{0,50}(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'ZTE' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: ZTE + model_replacement: $1 - regex: '; {0,2}(?:Blade|BLADE|ZTE-BLADE)([^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: 'ZTE Blade$1' - brand_replacement: 'ZTE' - model_replacement: 'Blade$1' + device_replacement: ZTE Blade$1 + brand_replacement: ZTE + model_replacement: Blade$1 - regex: '; {0,2}(?:Skate|SKATE|ZTE-SKATE)([^;/]*)(?: Build|\) AppleWebKit)' - device_replacement: 'ZTE Skate$1' - brand_replacement: 'ZTE' - model_replacement: 'Skate$1' + device_replacement: ZTE Skate$1 + brand_replacement: ZTE + model_replacement: Skate$1 - regex: '; {0,2}(Orange |Optimus )(Monte Carlo|San Francisco)(?: Build|\) AppleWebKit)' - device_replacement: '$1$2' - brand_replacement: 'ZTE' - model_replacement: '$1$2' - - regex: '; {0,2}(?:ZXY-ZTE_|ZTE\-U |ZTE[\- _]|ZTE-C[_ ])([^;/]{1,100}?)(?: Build|\) AppleWebKit)' - device_replacement: 'ZTE $1' - brand_replacement: 'ZTE' - model_replacement: '$1' - # operator specific + device_replacement: $1$2 + brand_replacement: ZTE + model_replacement: $1$2 + - regex: '; {0,2}(?:ZXY-ZTE_|ZTE\-U |ZTE[\- _]|ZTE-C[_ ])([^;/]{1,100}?)(?: Build|\) + AppleWebKit)' + device_replacement: ZTE $1 + brand_replacement: ZTE + model_replacement: $1 - regex: '; (BASE) (lutea|Lutea 2|Tab[^;]{0,200}?)(?: Build|\) AppleWebKit)' - device_replacement: '$1 $2' - brand_replacement: 'ZTE' - model_replacement: '$1 $2' - - regex: '; (Avea inTouch 2|soft stone|tmn smart a7|Movistar[ _]Link)(?: Build|\) AppleWebKit)' - regex_flag: 'i' - device_replacement: '$1' - brand_replacement: 'ZTE' - model_replacement: '$1' - - regex: '; {0,2}(vp9plus)\)' - device_replacement: '$1' - brand_replacement: 'ZTE' - model_replacement: '$1' - - ########## - # Zync - # @ref: http://www.zync.in/index.php/our-products/tablet-phablets - ########## - - regex: '; ?(Cloud[ _]Z5|z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900)(?: Build|\) AppleWebKit)' - device_replacement: '$1' - brand_replacement: 'Zync' - model_replacement: '$1' - - ########## - # Kindle - # @note: Needs to be after Sony Playstation Vita as this UA contains Silk/3.2 - # @ref: https://developer.amazon.com/sdk/fire/specifications.html - # @ref: http://amazonsilk.wordpress.com/useful-bits/silk-user-agent/ - ########## - - regex: '; ?(KFOT|Kindle Fire) Build\b' - device_replacement: 'Kindle Fire' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire' - - regex: '; ?(KFOTE|Amazon Kindle Fire2) Build\b' - device_replacement: 'Kindle Fire 2' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire 2' - - regex: '; ?(KFTT) Build\b' - device_replacement: 'Kindle Fire HD' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire HD 7"' - - regex: '; ?(KFJWI) Build\b' - device_replacement: 'Kindle Fire HD 8.9" WiFi' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire HD 8.9" WiFi' - - regex: '; ?(KFJWA) Build\b' - device_replacement: 'Kindle Fire HD 8.9" 4G' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire HD 8.9" 4G' - - regex: '; ?(KFSOWI) Build\b' - device_replacement: 'Kindle Fire HD 7" WiFi' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire HD 7" WiFi' - - regex: '; ?(KFTHWI) Build\b' - device_replacement: 'Kindle Fire HDX 7" WiFi' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire HDX 7" WiFi' - - regex: '; ?(KFTHWA) Build\b' - device_replacement: 'Kindle Fire HDX 7" 4G' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire HDX 7" 4G' - - regex: '; ?(KFAPWI) Build\b' - device_replacement: 'Kindle Fire HDX 8.9" WiFi' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire HDX 8.9" WiFi' - - regex: '; ?(KFAPWA) Build\b' - device_replacement: 'Kindle Fire HDX 8.9" 4G' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire HDX 8.9" 4G' - - regex: '; ?Amazon ([^;/]{1,100}) Build\b' - device_replacement: '$1' - brand_replacement: 'Amazon' - model_replacement: '$1' - - regex: '; ?(Kindle) Build\b' - device_replacement: 'Kindle' - brand_replacement: 'Amazon' - model_replacement: 'Kindle' - - regex: '; ?(Silk)/(\d+)\.(\d+)(?:\.([0-9\-]+)|) Build\b' - device_replacement: 'Kindle Fire' - brand_replacement: 'Amazon' - model_replacement: 'Kindle Fire$2' + device_replacement: $1 $2 + brand_replacement: ZTE + model_replacement: $1 $2 + - regex: '; (Avea inTouch 2|soft stone|tmn smart a7|Movistar[ _]Link)(?: Build|\) + AppleWebKit)' + regex_flag: i + device_replacement: $1 + brand_replacement: ZTE + model_replacement: $1 + - regex: ; {0,2}(vp9plus)\) + device_replacement: $1 + brand_replacement: ZTE + model_replacement: $1 + - regex: '; ?(Cloud[ _]Z5|z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900)(?: Build|\) + AppleWebKit)' + device_replacement: $1 + brand_replacement: Zync + model_replacement: $1 + - regex: ; ?(KFOT|Kindle Fire) Build\b + device_replacement: Kindle Fire + brand_replacement: Amazon + model_replacement: Kindle Fire + - regex: ; ?(KFOTE|Amazon Kindle Fire2) Build\b + device_replacement: Kindle Fire 2 + brand_replacement: Amazon + model_replacement: Kindle Fire 2 + - regex: ; ?(KFTT) Build\b + device_replacement: Kindle Fire HD + brand_replacement: Amazon + model_replacement: Kindle Fire HD 7" + - regex: ; ?(KFJWI) Build\b + device_replacement: Kindle Fire HD 8.9" WiFi + brand_replacement: Amazon + model_replacement: Kindle Fire HD 8.9" WiFi + - regex: ; ?(KFJWA) Build\b + device_replacement: Kindle Fire HD 8.9" 4G + brand_replacement: Amazon + model_replacement: Kindle Fire HD 8.9" 4G + - regex: ; ?(KFSOWI) Build\b + device_replacement: Kindle Fire HD 7" WiFi + brand_replacement: Amazon + model_replacement: Kindle Fire HD 7" WiFi + - regex: ; ?(KFTHWI) Build\b + device_replacement: Kindle Fire HDX 7" WiFi + brand_replacement: Amazon + model_replacement: Kindle Fire HDX 7" WiFi + - regex: ; ?(KFTHWA) Build\b + device_replacement: Kindle Fire HDX 7" 4G + brand_replacement: Amazon + model_replacement: Kindle Fire HDX 7" 4G + - regex: ; ?(KFAPWI) Build\b + device_replacement: Kindle Fire HDX 8.9" WiFi + brand_replacement: Amazon + model_replacement: Kindle Fire HDX 8.9" WiFi + - regex: ; ?(KFAPWA) Build\b + device_replacement: Kindle Fire HDX 8.9" 4G + brand_replacement: Amazon + model_replacement: Kindle Fire HDX 8.9" 4G + - regex: ; ?Amazon ([^;/]{1,100}) Build\b + device_replacement: $1 + brand_replacement: Amazon + model_replacement: $1 + - regex: ; ?(Kindle) Build\b + device_replacement: Kindle + brand_replacement: Amazon + model_replacement: Kindle + - regex: ; ?(Silk)/(\d+)\.(\d+)(?:\.([0-9\-]+)|) Build\b + device_replacement: Kindle Fire + brand_replacement: Amazon + model_replacement: Kindle Fire$2 - regex: ' (Kindle)/(\d+\.\d+)' - device_replacement: 'Kindle' - brand_replacement: 'Amazon' - model_replacement: '$1 $2' + device_replacement: Kindle + brand_replacement: Amazon + model_replacement: $1 $2 - regex: ' (Silk|Kindle)/(\d+)\.' - device_replacement: 'Kindle' - brand_replacement: 'Amazon' - model_replacement: 'Kindle' - - ######### - # Devices from chinese manufacturer(s) - # @note: identified by x-wap-profile http://218.249.47.94/Xianghe/.{0,200} - ######### - - regex: '(sprd)\-([^/]{1,50})/' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - # @ref: http://eshinechina.en.alibaba.com/ - - regex: '; {0,2}(H\d{2}00\+?) Build' - device_replacement: '$1' - brand_replacement: 'Hero' - model_replacement: '$1' - - regex: '; {0,2}(iphone|iPhone5) Build/' - device_replacement: 'Xianghe $1' - brand_replacement: 'Xianghe' - model_replacement: '$1' - - regex: '; {0,2}(e\d{4}[a-z]?_?v\d+|v89_[^;/]{1,100})[^;/]{1,30} Build/' - device_replacement: 'Xianghe $1' - brand_replacement: 'Xianghe' - model_replacement: '$1' - - ######### - # Cellular - # @ref: - # @note: Operator branded devices - ######### - - regex: '\bUSCC[_\-]?([^ ;/\)]+)' - device_replacement: '$1' - brand_replacement: 'Cellular' - model_replacement: '$1' - - ###################################################################### - # Windows Phone Parsers - ###################################################################### - - ######### - # Alcatel Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:ALCATEL)[^;]{0,200}; {0,2}([^;,\)]+)' - device_replacement: 'Alcatel $1' - brand_replacement: 'Alcatel' - model_replacement: '$1' - - ######### - # Asus Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:ASUS|Asus)[^;]{0,200}; {0,2}([^;,\)]+)' - device_replacement: 'Asus $1' - brand_replacement: 'Asus' - model_replacement: '$1' - - ######### - # Dell Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:DELL|Dell)[^;]{0,200}; {0,2}([^;,\)]+)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: '$1' - - ######### - # HTC Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:HTC|Htc|HTC_blocked[^;]{0,200})[^;]{0,200}; {0,2}(?:HTC|)([^;,\)]+)' - device_replacement: 'HTC $1' - brand_replacement: 'HTC' - model_replacement: '$1' - - ######### - # Huawei Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:HUAWEI)[^;]{0,200}; {0,2}(?:HUAWEI |)([^;,\)]+)' - device_replacement: 'Huawei $1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - ######### - # LG Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:LG|Lg)[^;]{0,200}; {0,2}(?:LG[ \-]|)([^;,\)]+)' - device_replacement: 'LG $1' - brand_replacement: 'LG' - model_replacement: '$1' - - ######### - # Noka Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:rv:11; |)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?|)(\d{3,10}[^;\)]*)' - device_replacement: 'Lumia $1' - brand_replacement: 'Nokia' - model_replacement: 'Lumia $1' - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(RM-\d{3,})' - device_replacement: 'Nokia $1' - brand_replacement: 'Nokia' - model_replacement: '$1' - - regex: '(?:Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)]|WPDesktop;) ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(?:NOKIA ?|Nokia ?|LUMIA ?|[Ll]umia ?|)([^;\)]+)' - device_replacement: 'Nokia $1' - brand_replacement: 'Nokia' - model_replacement: '$1' - - ######### - # Microsoft Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|)(?:Microsoft(?: Corporation|))[^;]{0,200}; {0,2}([^;,\)]+)' - device_replacement: 'Microsoft $1' - brand_replacement: 'Microsoft' - model_replacement: '$1' - - ######### - # Samsung Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:SAMSUNG)[^;]{0,200}; {0,2}(?:SAMSUNG |)([^;,\.\)]+)' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - ######### - # Toshiba Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)(?:TOSHIBA|FujitsuToshibaMobileCommun)[^;]{0,200}; {0,2}([^;,\)]+)' - device_replacement: 'Toshiba $1' - brand_replacement: 'Toshiba' - model_replacement: '$1' - - ######### - # Generic Windows Phones - ######### - - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; ?|Touch; ?|WpsLondonTest; ?|)([^;]{1,200}); {0,2}([^;,\)]+)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ###################################################################### - # Other Devices Parser - ###################################################################### - - ######### - # Samsung Bada Phones - ######### - - regex: '(?:^|; )SAMSUNG\-([A-Za-z0-9\-]{1,50}).{0,200} Bada/' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - ######### - # Firefox OS - ######### - - regex: '\(Mobile; ALCATEL ?(One|ONE) ?(Touch|TOUCH) ?([^;/]{1,100}?)(?:/[^;]{1,200}|); rv:[^\)]{1,200}\) Gecko/[^\/]{1,200} Firefox/' - device_replacement: 'Alcatel $1 $2 $3' - brand_replacement: 'Alcatel' - model_replacement: 'One Touch $3' - - regex: '\(Mobile; (?:ZTE([^;]{1,200})|(OpenC)); rv:[^\)]{1,200}\) Gecko/[^\/]{1,200} Firefox/' - device_replacement: 'ZTE $1$2' - brand_replacement: 'ZTE' - model_replacement: '$1$2' - - ######### - # KaiOS - ######### - - regex: '\(Mobile; ALCATEL([A-Za-z0-9\-]+); rv:[^\)]{1,200}\) Gecko/[^\/]{1,200} Firefox/[^\/]{1,200} KaiOS/' - device_replacement: 'Alcatel $1' - brand_replacement: 'Alcatel' - model_replacement: '$1' - - regex: '\(Mobile; LYF\/([A-Za-z0-9\-]{1,100})\/.{0,100};.{0,100}rv:[^\)]{1,100}\) Gecko/[^\/]{1,100} Firefox/[^\/]{1,100} KAIOS/' - device_replacement: 'LYF $1' - brand_replacement: 'LYF' - model_replacement: '$1' - - regex: '\(Mobile; Nokia_([A-Za-z0-9\-]{1,100})_.{1,100}; rv:[^\)]{1,100}\) Gecko/[^\/]{1,100} Firefox/[^\/]{1,100} KAIOS/' - device_replacement: 'Nokia $1' - brand_replacement: 'Nokia' - model_replacement: '$1' - - ########## - # NOKIA - # @note: NokiaN8-00 comes before iphone. Sometimes spoofs iphone - ########## - - regex: 'Nokia(N[0-9]+)([A-Za-z_\-][A-Za-z0-9_\-]*)' - device_replacement: 'Nokia $1' - brand_replacement: 'Nokia' - model_replacement: '$1$2' - - regex: '(?:NOKIA|Nokia)(?:\-| {0,2})(?:([A-Za-z0-9]+)\-[0-9a-f]{32}|([A-Za-z0-9\-]+)(?:UCBrowser)|([A-Za-z0-9\-]+))' - device_replacement: 'Nokia $1$2$3' - brand_replacement: 'Nokia' - model_replacement: '$1$2$3' - - regex: 'Lumia ([A-Za-z0-9\-]+)' - device_replacement: 'Lumia $1' - brand_replacement: 'Nokia' - model_replacement: 'Lumia $1' - # UCWEB Browser on Symbian - - regex: '\(Symbian; U; S60 V5; [A-z]{2}\-[A-z]{2}; (SonyEricsson|Samsung|Nokia|LG)([^;/]{1,100}?)\)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - # Nokia Symbian - - regex: '\(Symbian(?:/3|); U; ([^;]{1,200});' - device_replacement: 'Nokia $1' - brand_replacement: 'Nokia' - model_replacement: '$1' - - ########## - # BlackBerry - # @ref: http://www.useragentstring.com/pages/BlackBerry/ - ########## - - regex: 'BB10; ([A-Za-z0-9\- ]+)\)' - device_replacement: 'BlackBerry $1' - brand_replacement: 'BlackBerry' - model_replacement: '$1' - - regex: 'Play[Bb]ook.{1,200}RIM Tablet OS' - device_replacement: 'BlackBerry Playbook' - brand_replacement: 'BlackBerry' - model_replacement: 'Playbook' - - regex: 'Black[Bb]erry ([0-9]+);' - device_replacement: 'BlackBerry $1' - brand_replacement: 'BlackBerry' - model_replacement: '$1' - - regex: 'Black[Bb]erry([0-9]+)' - device_replacement: 'BlackBerry $1' - brand_replacement: 'BlackBerry' - model_replacement: '$1' - - regex: 'Black[Bb]erry;' - device_replacement: 'BlackBerry' - brand_replacement: 'BlackBerry' - - ########## - # PALM / HP - # @note: some palm devices must come before iphone. sometimes spoofs iphone in ua - ########## - - regex: '(Pre|Pixi)/\d+\.\d+' - device_replacement: 'Palm $1' - brand_replacement: 'Palm' - model_replacement: '$1' - - regex: 'Palm([0-9]+)' - device_replacement: 'Palm $1' - brand_replacement: 'Palm' - model_replacement: '$1' - - regex: 'Treo([A-Za-z0-9]+)' - device_replacement: 'Palm Treo $1' - brand_replacement: 'Palm' - model_replacement: 'Treo $1' - - regex: 'webOS.{0,200}(P160U(?:NA|))/(\d+).(\d+)' - device_replacement: 'HP Veer' - brand_replacement: 'HP' - model_replacement: 'Veer' - - regex: '(Touch[Pp]ad)/\d+\.\d+' - device_replacement: 'HP TouchPad' - brand_replacement: 'HP' - model_replacement: 'TouchPad' - - regex: 'HPiPAQ([A-Za-z0-9]{1,20})/\d+\.\d+' - device_replacement: 'HP iPAQ $1' - brand_replacement: 'HP' - model_replacement: 'iPAQ $1' - - regex: 'PDA; (PalmOS)/sony/model ([a-z]+)/Revision' - device_replacement: '$1' - brand_replacement: 'Sony' - model_replacement: '$1 $2' - - ########## - # AppleTV - # No built in browser that I can tell - # Stack Overflow indicated iTunes-AppleTV/4.1 as a known UA for app available and I'm seeing it in live traffic - ########## - - regex: '(Apple\s?TV)' - device_replacement: 'AppleTV' - brand_replacement: 'Apple' - model_replacement: 'AppleTV' - - ######### - # Tesla Model S - ######### - - regex: '(QtCarBrowser)' - device_replacement: 'Tesla Model S' - brand_replacement: 'Tesla' - model_replacement: 'Model S' - - ########## - # iSTUFF - # @note: complete but probably catches spoofs - # ipad and ipod must be parsed before iphone - # cannot determine specific device type from ua string. (3g, 3gs, 4, etc) - ########## - # @note: on some ua the device can be identified e.g. iPhone5,1 - - regex: '(iPhone|iPad|iPod)(\d+,\d+)' - device_replacement: '$1' - brand_replacement: 'Apple' - model_replacement: '$1$2' - # @note: iPad needs to be before iPhone - - regex: '(iPad)(?:;| Simulator;)' - device_replacement: '$1' - brand_replacement: 'Apple' - model_replacement: '$1' - - regex: '(iPod)(?:;| touch;| Simulator;)' - device_replacement: '$1' - brand_replacement: 'Apple' - model_replacement: '$1' - - regex: '(iPhone)(?:;| Simulator;)' - device_replacement: '$1' - brand_replacement: 'Apple' - model_replacement: '$1' - - regex: '(Watch)(\d+,\d+)' - device_replacement: 'Apple $1' - brand_replacement: 'Apple' - model_replacement: '$1$2' - - regex: '(Apple Watch)(?:;| Simulator;)' - device_replacement: '$1' - brand_replacement: 'Apple' - model_replacement: '$1' - - regex: '(HomePod)(?:;| Simulator;)' - device_replacement: '$1' - brand_replacement: 'Apple' - model_replacement: '$1' - - regex: 'iPhone' - device_replacement: 'iPhone' - brand_replacement: 'Apple' - model_replacement: 'iPhone' - # @note: desktop applications show device info - - regex: 'CFNetwork/.{0,100} Darwin/\d.{0,100}\(((?:Mac|iMac|PowerMac|PowerBook)[^\d]*)(\d+)(?:,|%2C)(\d+)' - device_replacement: '$1$2,$3' - brand_replacement: 'Apple' - model_replacement: '$1$2,$3' - # @note: newer desktop applications don't show device info - # This is here so as to not have them recorded as iOS-Device - - regex: 'CFNetwork/.{0,100} Darwin/\d+\.\d+\.\d+ \(x86_64\)' - device_replacement: 'Mac' - brand_replacement: 'Apple' - model_replacement: 'Mac' - # @note: iOS applications do not show device info - - regex: 'CFNetwork/.{0,100} Darwin/\d' - device_replacement: 'iOS-Device' - brand_replacement: 'Apple' - model_replacement: 'iOS-Device' - - ########################## - # Outlook on iOS >= 2.62.0 - ########################## - - regex: 'Outlook-(iOS)/\d+\.\d+\.prod\.iphone' - brand_replacement: 'Apple' - device_replacement: 'iPhone' - model_replacement: 'iPhone' - - ########## - # Acer - ########## - - regex: 'acer_([A-Za-z0-9]+)_' - device_replacement: 'Acer $1' - brand_replacement: 'Acer' - model_replacement: '$1' - - ########## - # Alcatel - ########## - - regex: '(?:ALCATEL|Alcatel)-([A-Za-z0-9\-]+)' - device_replacement: 'Alcatel $1' - brand_replacement: 'Alcatel' - model_replacement: '$1' - - ########## - # Amoi - ########## - - regex: '(?:Amoi|AMOI)\-([A-Za-z0-9]+)' - device_replacement: 'Amoi $1' - brand_replacement: 'Amoi' - model_replacement: '$1' - - ########## - # Asus - ########## - - regex: '(?:; |\/|^)((?:Transformer (?:Pad|Prime) |Transformer |PadFone[ _]?)[A-Za-z0-9]*)' - device_replacement: 'Asus $1' - brand_replacement: 'Asus' - model_replacement: '$1' - - regex: '(?:asus.{0,200}?ASUS|Asus|ASUS|asus)[\- ;]*((?:Transformer (?:Pad|Prime) |Transformer |Padfone |Nexus[ _]|)[A-Za-z0-9]+)' - device_replacement: 'Asus $1' - brand_replacement: 'Asus' - model_replacement: '$1' - - regex: '(?:ASUS)_([A-Za-z0-9\-]+)' - device_replacement: 'Asus $1' - brand_replacement: 'Asus' - model_replacement: '$1' - - - ########## - # Bird - ########## - - regex: '\bBIRD[ \-\.]([A-Za-z0-9]+)' - device_replacement: 'Bird $1' - brand_replacement: 'Bird' - model_replacement: '$1' - - ########## - # Dell - ########## - - regex: '\bDell ([A-Za-z0-9]+)' - device_replacement: 'Dell $1' - brand_replacement: 'Dell' - model_replacement: '$1' - - ########## - # DoCoMo - ########## - - regex: 'DoCoMo/2\.0 ([A-Za-z0-9]+)' - device_replacement: 'DoCoMo $1' - brand_replacement: 'DoCoMo' - model_replacement: '$1' - - regex: '^.{0,50}?([A-Za-z0-9]{1,30})_W;FOMA' - device_replacement: 'DoCoMo $1' - brand_replacement: 'DoCoMo' - model_replacement: '$1' - - regex: '^.{0,50}?([A-Za-z0-9]{1,30});FOMA' - device_replacement: 'DoCoMo $1' - brand_replacement: 'DoCoMo' - model_replacement: '$1' - - ########## - # htc - ########## - - regex: '\b(?:HTC/|HTC/[a-z0-9]{1,20}/|)HTC[ _\-;]? {0,2}(.{0,200}?)(?:-?Mozilla|fingerPrint|[;/\(\)]|$)' - device_replacement: 'HTC $1' - brand_replacement: 'HTC' - model_replacement: '$1' - - ########## - # Huawei - ########## - - regex: 'Huawei([A-Za-z0-9]+)' - device_replacement: 'Huawei $1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - regex: 'HUAWEI-([A-Za-z0-9]+)' - device_replacement: 'Huawei $1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - regex: 'HUAWEI ([A-Za-z0-9\-]+)' - device_replacement: 'Huawei $1' - brand_replacement: 'Huawei' - model_replacement: '$1' - - regex: 'vodafone([A-Za-z0-9]+)' - device_replacement: 'Huawei Vodafone $1' - brand_replacement: 'Huawei' - model_replacement: 'Vodafone $1' - - ########## - # i-mate - ########## - - regex: 'i\-mate ([A-Za-z0-9]+)' - device_replacement: 'i-mate $1' - brand_replacement: 'i-mate' - model_replacement: '$1' - - ########## - # kyocera - ########## - - regex: 'Kyocera\-([A-Za-z0-9]+)' - device_replacement: 'Kyocera $1' - brand_replacement: 'Kyocera' - model_replacement: '$1' - - regex: 'KWC\-([A-Za-z0-9]+)' - device_replacement: 'Kyocera $1' - brand_replacement: 'Kyocera' - model_replacement: '$1' - - ########## - # lenovo - ########## - - regex: 'Lenovo[_\-]([A-Za-z0-9]+)' - device_replacement: 'Lenovo $1' - brand_replacement: 'Lenovo' - model_replacement: '$1' - - ########## - # HbbTV (European and Australian standard) - # written before the LG regexes, as LG is making HbbTV too - ########## - - regex: '(HbbTV)/[0-9]+\.[0-9]+\.[0-9]+ \( ?;(LG)E ?;([^;]{0,30})' - device_replacement: '$1' - brand_replacement: '$2' - model_replacement: '$3' - - regex: '(HbbTV)/1\.1\.1.{0,200}CE-HTML/1\.\d;(Vendor/|)(THOM[^;]{0,200}?)[;\s].{0,30}(LF[^;]{1,200});?' - device_replacement: '$1' - brand_replacement: 'Thomson' - model_replacement: '$4' - - regex: '(HbbTV)(?:/1\.1\.1|) ?(?: \(;;;;;\)|); {0,2}CE-HTML(?:/1\.\d|); {0,2}([^ ]{1,30}) ([^;]{1,200});' - device_replacement: '$1' - brand_replacement: '$2' - model_replacement: '$3' - - regex: '(HbbTV)/1\.1\.1 \(;;;;;\) Maple_2011' - device_replacement: '$1' - brand_replacement: 'Samsung' - - regex: '(HbbTV)/[0-9]+\.[0-9]+\.[0-9]+ \([^;]{0,30}; ?(?:CUS:([^;]{0,200})|([^;]{1,200})) ?; ?([^;]{0,30})' - device_replacement: '$1' - brand_replacement: '$2$3' - model_replacement: '$4' - - regex: '(HbbTV)/[0-9]+\.[0-9]+\.[0-9]+' - device_replacement: '$1' - - ########## - # LGE NetCast TV - ########## - - regex: 'LGE; (?:Media\/|)([^;]{0,200});[^;]{0,200};[^;]{0,200};?\); "?LG NetCast(\.TV|\.Media|)-\d+' - device_replacement: 'NetCast$2' - brand_replacement: 'LG' - model_replacement: '$1' - - ########## - # InettvBrowser - ########## - - regex: 'InettvBrowser/[0-9]{1,30}\.[0-9A-Z]{1,30} \([^;]{0,200};(Sony)([^;]{0,200});[^;]{0,200};[^\)]{0,10}\)' - device_replacement: 'Inettv' - brand_replacement: '$1' - model_replacement: '$2' - - regex: 'InettvBrowser/[0-9]{1,30}\.[0-9A-Z]{1,30} \([^;]{0,200};([^;]{0,200});[^;]{0,200};[^\)]{0,10}\)' - device_replacement: 'Inettv' - brand_replacement: 'Generic_Inettv' - model_replacement: '$1' - - regex: '(?:InettvBrowser|TSBNetTV|NETTV|HBBTV)' - device_replacement: 'Inettv' - brand_replacement: 'Generic_Inettv' - - ########## - # lg - ########## - # LG Symbian Phones - - regex: 'Series60/\d\.\d (LG)[\-]?([A-Za-z0-9 \-]+)' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - # other LG phones - - regex: '\b(?:LGE[ \-]LG\-(?:AX|)|LGE |LGE?-LG|LGE?[ \-]|LG[ /\-]|lg[\-])([A-Za-z0-9]+)\b' - device_replacement: 'LG $1' - brand_replacement: 'LG' - model_replacement: '$1' - - regex: '(?:^LG[\-]?|^LGE[\-/]?)([A-Za-z]+[0-9]+[A-Za-z]*)' - device_replacement: 'LG $1' - brand_replacement: 'LG' - model_replacement: '$1' - - regex: '^LG([0-9]+[A-Za-z]*)' - device_replacement: 'LG $1' - brand_replacement: 'LG' - model_replacement: '$1' - - ########## - # microsoft - ########## - - regex: '(KIN\.[^ ]+) (\d+)\.(\d+)' - device_replacement: 'Microsoft $1' - brand_replacement: 'Microsoft' - model_replacement: '$1' - - regex: '(?:MSIE|XBMC).{0,200}\b(Xbox)\b' - device_replacement: '$1' - brand_replacement: 'Microsoft' - model_replacement: '$1' - - regex: '; ARM; Trident/6\.0; Touch[\);]' - device_replacement: 'Microsoft Surface RT' - brand_replacement: 'Microsoft' - model_replacement: 'Surface RT' - - ########## - # motorola - ########## - - regex: 'Motorola\-([A-Za-z0-9]+)' - device_replacement: 'Motorola $1' - brand_replacement: 'Motorola' - model_replacement: '$1' - - regex: 'MOTO\-([A-Za-z0-9]+)' - device_replacement: 'Motorola $1' - brand_replacement: 'Motorola' - model_replacement: '$1' - - regex: 'MOT\-([A-z0-9][A-z0-9\-]*)' - device_replacement: 'Motorola $1' - brand_replacement: 'Motorola' - model_replacement: '$1' + device_replacement: Kindle + brand_replacement: Amazon + model_replacement: Kindle + - regex: (sprd)\-([^/]{1,50})/ + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 + - regex: ; {0,2}(H\d{2}00\+?) Build + device_replacement: $1 + brand_replacement: Hero + model_replacement: $1 + - regex: ; {0,2}(iphone|iPhone5) Build/ + device_replacement: Xianghe $1 + brand_replacement: Xianghe + model_replacement: $1 + - regex: ; {0,2}(e\d{4}[a-z]?_?v\d+|v89_[^;/]{1,100})[^;/]{1,30} Build/ + device_replacement: Xianghe $1 + brand_replacement: Xianghe + model_replacement: $1 + - regex: \bUSCC[_\-]?([^ ;/\)]+) + device_replacement: $1 + brand_replacement: Cellular + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|)(?:ALCATEL)[^;]{0,200}; {0,2}([^;,\)]+) + device_replacement: Alcatel $1 + brand_replacement: Alcatel + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|WpsLondonTest; ?|)(?:ASUS|Asus)[^;]{0,200}; {0,2}([^;,\)]+) + device_replacement: Asus $1 + brand_replacement: Asus + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|)(?:DELL|Dell)[^;]{0,200}; {0,2}([^;,\)]+) + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|WpsLondonTest; ?|)(?:HTC|Htc|HTC_blocked[^;]{0,200})[^;]{0,200}; + {0,2}(?:HTC|)([^;,\)]+) + device_replacement: HTC $1 + brand_replacement: HTC + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|)(?:HUAWEI)[^;]{0,200}; {0,2}(?:HUAWEI |)([^;,\)]+) + device_replacement: Huawei $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|)(?:LG|Lg)[^;]{0,200}; {0,2}(?:LG[ \-]|)([^;,\)]+) + device_replacement: LG $1 + brand_replacement: LG + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|)(?:rv:11; |)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(?:NOKIA ?|Nokia ?|LUMIA + ?|[Ll]umia ?|)(\d{3,10}[^;\)]*) + device_replacement: Lumia $1 + brand_replacement: Nokia + model_replacement: Lumia $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(RM-\d{3,}) + device_replacement: Nokia $1 + brand_replacement: Nokia + model_replacement: $1 + - regex: (?:Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)]|WPDesktop;) + ?(?:ARM; ?Touch; ?|Touch; ?|)(?:NOKIA|Nokia)[^;]{0,200}; {0,2}(?:NOKIA ?|Nokia + ?|LUMIA ?|[Ll]umia ?|)([^;\)]+) + device_replacement: Nokia $1 + brand_replacement: Nokia + model_replacement: $1 + - regex: 'Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|)(?:Microsoft(?: Corporation|))[^;]{0,200}; {0,2}([^;,\)]+)' + device_replacement: Microsoft $1 + brand_replacement: Microsoft + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|WpsLondonTest; ?|)(?:SAMSUNG)[^;]{0,200}; {0,2}(?:SAMSUNG |)([^;,\.\)]+) + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|WpsLondonTest; ?|)(?:TOSHIBA|FujitsuToshibaMobileCommun)[^;]{0,200}; + {0,2}([^;,\)]+) + device_replacement: Toshiba $1 + brand_replacement: Toshiba + model_replacement: $1 + - regex: Windows Phone [^;]{1,30}; .{0,100}?IEMobile/[^;\)]+[;\)] ?(?:ARM; ?Touch; + ?|Touch; ?|WpsLondonTest; ?|)([^;]{1,200}); {0,2}([^;,\)]+) + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 + - regex: (?:^|; )SAMSUNG\-([A-Za-z0-9\-]{1,50}).{0,200} Bada/ + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: \(Mobile; ALCATEL ?(One|ONE) ?(Touch|TOUCH) ?([^;/]{1,100}?)(?:/[^;]{1,200}|); + rv:[^\)]{1,200}\) Gecko/[^\/]{1,200} Firefox/ + device_replacement: Alcatel $1 $2 $3 + brand_replacement: Alcatel + model_replacement: One Touch $3 + - regex: \(Mobile; (?:ZTE([^;]{1,200})|(OpenC)); rv:[^\)]{1,200}\) Gecko/[^\/]{1,200} + Firefox/ + device_replacement: ZTE $1$2 + brand_replacement: ZTE + model_replacement: $1$2 + - regex: \(Mobile; ALCATEL([A-Za-z0-9\-]+); rv:[^\)]{1,200}\) Gecko/[^\/]{1,200} + Firefox/[^\/]{1,200} KaiOS/ + device_replacement: Alcatel $1 + brand_replacement: Alcatel + model_replacement: $1 + - regex: \(Mobile; LYF\/([A-Za-z0-9\-]{1,100})\/.{0,100};.{0,100}rv:[^\)]{1,100}\) + Gecko/[^\/]{1,100} Firefox/[^\/]{1,100} KAIOS/ + device_replacement: LYF $1 + brand_replacement: LYF + model_replacement: $1 + - regex: \(Mobile; Nokia_([A-Za-z0-9\-]{1,100})_.{1,100}; rv:[^\)]{1,100}\) Gecko/[^\/]{1,100} + Firefox/[^\/]{1,100} KAIOS/ + device_replacement: Nokia $1 + brand_replacement: Nokia + model_replacement: $1 + - regex: Nokia(N[0-9]+)([A-Za-z_\-][A-Za-z0-9_\-]*) + device_replacement: Nokia $1 + brand_replacement: Nokia + model_replacement: $1$2 + - regex: (?:NOKIA|Nokia)(?:\-| {0,2})(?:([A-Za-z0-9]+)\-[0-9a-f]{32}|([A-Za-z0-9\-]+)(?:UCBrowser)|([A-Za-z0-9\-]+)) + device_replacement: Nokia $1$2$3 + brand_replacement: Nokia + model_replacement: $1$2$3 + - regex: Lumia ([A-Za-z0-9\-]+) + device_replacement: Lumia $1 + brand_replacement: Nokia + model_replacement: Lumia $1 + - regex: \(Symbian; U; S60 V5; [A-z]{2}\-[A-z]{2}; (SonyEricsson|Samsung|Nokia|LG)([^;/]{1,100}?)\) + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 + - regex: \(Symbian(?:/3|); U; ([^;]{1,200}); + device_replacement: Nokia $1 + brand_replacement: Nokia + model_replacement: $1 + - regex: BB10; ([A-Za-z0-9\- ]+)\) + device_replacement: BlackBerry $1 + brand_replacement: BlackBerry + model_replacement: $1 + - regex: Play[Bb]ook.{1,200}RIM Tablet OS + device_replacement: BlackBerry Playbook + brand_replacement: BlackBerry + model_replacement: Playbook + - regex: Black[Bb]erry ([0-9]+); + device_replacement: BlackBerry $1 + brand_replacement: BlackBerry + model_replacement: $1 + - regex: Black[Bb]erry([0-9]+) + device_replacement: BlackBerry $1 + brand_replacement: BlackBerry + model_replacement: $1 + - regex: Black[Bb]erry; + device_replacement: BlackBerry + brand_replacement: BlackBerry + - regex: (Pre|Pixi)/\d+\.\d+ + device_replacement: Palm $1 + brand_replacement: Palm + model_replacement: $1 + - regex: Palm([0-9]+) + device_replacement: Palm $1 + brand_replacement: Palm + model_replacement: $1 + - regex: Treo([A-Za-z0-9]+) + device_replacement: Palm Treo $1 + brand_replacement: Palm + model_replacement: Treo $1 + - regex: webOS.{0,200}(P160U(?:NA|))/(\d+).(\d+) + device_replacement: HP Veer + brand_replacement: HP + model_replacement: Veer + - regex: (Touch[Pp]ad)/\d+\.\d+ + device_replacement: HP TouchPad + brand_replacement: HP + model_replacement: TouchPad + - regex: HPiPAQ([A-Za-z0-9]{1,20})/\d+\.\d+ + device_replacement: HP iPAQ $1 + brand_replacement: HP + model_replacement: iPAQ $1 + - regex: PDA; (PalmOS)/sony/model ([a-z]+)/Revision + device_replacement: $1 + brand_replacement: Sony + model_replacement: $1 $2 + - regex: (Apple\s?TV) + device_replacement: AppleTV + brand_replacement: Apple + model_replacement: AppleTV + - regex: (QtCarBrowser) + device_replacement: Tesla Model S + brand_replacement: Tesla + model_replacement: Model S + - regex: (iPhone|iPad|iPod)(\d+,\d+) + device_replacement: $1 + brand_replacement: Apple + model_replacement: $1$2 + - regex: (iPad)(?:;| Simulator;) + device_replacement: $1 + brand_replacement: Apple + model_replacement: $1 + - regex: (iPod)(?:;| touch;| Simulator;) + device_replacement: $1 + brand_replacement: Apple + model_replacement: $1 + - regex: (iPhone)(?:;| Simulator;) + device_replacement: $1 + brand_replacement: Apple + model_replacement: $1 + - regex: (Watch)(\d+,\d+) + device_replacement: Apple $1 + brand_replacement: Apple + model_replacement: $1$2 + - regex: (Apple Watch)(?:;| Simulator;) + device_replacement: $1 + brand_replacement: Apple + model_replacement: $1 + - regex: (HomePod)(?:;| Simulator;) + device_replacement: $1 + brand_replacement: Apple + model_replacement: $1 + - regex: iPhone + device_replacement: iPhone + brand_replacement: Apple + model_replacement: iPhone + - regex: CFNetwork/.{0,100} Darwin/\d.{0,100}\(((?:Mac|iMac|PowerMac|PowerBook)[^\d]*)(\d+)(?:,|%2C)(\d+) + device_replacement: $1$2,$3 + brand_replacement: Apple + model_replacement: $1$2,$3 + - regex: CFNetwork/.{0,100} Darwin/\d+\.\d+\.\d+ \(x86_64\) + device_replacement: Mac + brand_replacement: Apple + model_replacement: Mac + - regex: CFNetwork/.{0,100} Darwin/\d + device_replacement: iOS-Device + brand_replacement: Apple + model_replacement: iOS-Device + - regex: Outlook-(iOS)/\d+\.\d+\.prod\.iphone + brand_replacement: Apple + device_replacement: iPhone + model_replacement: iPhone + - regex: acer_([A-Za-z0-9]+)_ + device_replacement: Acer $1 + brand_replacement: Acer + model_replacement: $1 + - regex: (?:ALCATEL|Alcatel)-([A-Za-z0-9\-]+) + device_replacement: Alcatel $1 + brand_replacement: Alcatel + model_replacement: $1 + - regex: (?:Amoi|AMOI)\-([A-Za-z0-9]+) + device_replacement: Amoi $1 + brand_replacement: Amoi + model_replacement: $1 + - regex: (?:; |\/|^)((?:Transformer (?:Pad|Prime) |Transformer |PadFone[ _]?)[A-Za-z0-9]*) + device_replacement: Asus $1 + brand_replacement: Asus + model_replacement: $1 + - regex: (?:asus.{0,200}?ASUS|Asus|ASUS|asus)[\- ;]*((?:Transformer (?:Pad|Prime) + |Transformer |Padfone |Nexus[ _]|)[A-Za-z0-9]+) + device_replacement: Asus $1 + brand_replacement: Asus + model_replacement: $1 + - regex: (?:ASUS)_([A-Za-z0-9\-]+) + device_replacement: Asus $1 + brand_replacement: Asus + model_replacement: $1 + - regex: \bBIRD[ \-\.]([A-Za-z0-9]+) + device_replacement: Bird $1 + brand_replacement: Bird + model_replacement: $1 + - regex: \bDell ([A-Za-z0-9]+) + device_replacement: Dell $1 + brand_replacement: Dell + model_replacement: $1 + - regex: DoCoMo/2\.0 ([A-Za-z0-9]+) + device_replacement: DoCoMo $1 + brand_replacement: DoCoMo + model_replacement: $1 + - regex: ^.{0,50}?([A-Za-z0-9]{1,30})_W;FOMA + device_replacement: DoCoMo $1 + brand_replacement: DoCoMo + model_replacement: $1 + - regex: ^.{0,50}?([A-Za-z0-9]{1,30});FOMA + device_replacement: DoCoMo $1 + brand_replacement: DoCoMo + model_replacement: $1 + - regex: \b(?:HTC/|HTC/[a-z0-9]{1,20}/|)HTC[ _\-;]? {0,2}(.{0,200}?)(?:-?Mozilla|fingerPrint|[;/\(\)]|$) + device_replacement: HTC $1 + brand_replacement: HTC + model_replacement: $1 + - regex: Huawei([A-Za-z0-9]+) + device_replacement: Huawei $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: HUAWEI-([A-Za-z0-9]+) + device_replacement: Huawei $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: HUAWEI ([A-Za-z0-9\-]+) + device_replacement: Huawei $1 + brand_replacement: Huawei + model_replacement: $1 + - regex: vodafone([A-Za-z0-9]+) + device_replacement: Huawei Vodafone $1 + brand_replacement: Huawei + model_replacement: Vodafone $1 + - regex: i\-mate ([A-Za-z0-9]+) + device_replacement: i-mate $1 + brand_replacement: i-mate + model_replacement: $1 + - regex: Kyocera\-([A-Za-z0-9]+) + device_replacement: Kyocera $1 + brand_replacement: Kyocera + model_replacement: $1 + - regex: KWC\-([A-Za-z0-9]+) + device_replacement: Kyocera $1 + brand_replacement: Kyocera + model_replacement: $1 + - regex: Lenovo[_\-]([A-Za-z0-9]+) + device_replacement: Lenovo $1 + brand_replacement: Lenovo + model_replacement: $1 + - regex: (HbbTV)/[0-9]+\.[0-9]+\.[0-9]+ \( ?;(LG)E ?;([^;]{0,30}) + device_replacement: $1 + brand_replacement: $2 + model_replacement: $3 + - regex: (HbbTV)/1\.1\.1.{0,200}CE-HTML/1\.\d;(Vendor/|)(THOM[^;]{0,200}?)[;\s].{0,30}(LF[^;]{1,200});? + device_replacement: $1 + brand_replacement: Thomson + model_replacement: $4 + - regex: '(HbbTV)(?:/1\.1\.1|) ?(?: \(;;;;;\)|); {0,2}CE-HTML(?:/1\.\d|); {0,2}([^ + ]{1,30}) ([^;]{1,200});' + device_replacement: $1 + brand_replacement: $2 + model_replacement: $3 + - regex: (HbbTV)/1\.1\.1 \(;;;;;\) Maple_2011 + device_replacement: $1 + brand_replacement: Samsung + - regex: (HbbTV)/[0-9]+\.[0-9]+\.[0-9]+ \([^;]{0,30}; ?(?:CUS:([^;]{0,200})|([^;]{1,200})) + ?; ?([^;]{0,30}) + device_replacement: $1 + brand_replacement: $2$3 + model_replacement: $4 + - regex: (HbbTV)/[0-9]+\.[0-9]+\.[0-9]+ + device_replacement: $1 + - regex: LGE; (?:Media\/|)([^;]{0,200});[^;]{0,200};[^;]{0,200};?\); "?LG NetCast(\.TV|\.Media|)-\d+ + device_replacement: NetCast$2 + brand_replacement: LG + model_replacement: $1 + - regex: InettvBrowser/[0-9]{1,30}\.[0-9A-Z]{1,30} \([^;]{0,200};(Sony)([^;]{0,200});[^;]{0,200};[^\)]{0,10}\) + device_replacement: Inettv + brand_replacement: $1 + model_replacement: $2 + - regex: InettvBrowser/[0-9]{1,30}\.[0-9A-Z]{1,30} \([^;]{0,200};([^;]{0,200});[^;]{0,200};[^\)]{0,10}\) + device_replacement: Inettv + brand_replacement: Generic_Inettv + model_replacement: $1 + - regex: (?:InettvBrowser|TSBNetTV|NETTV|HBBTV) + device_replacement: Inettv + brand_replacement: Generic_Inettv + - regex: Series60/\d\.\d (LG)[\-]?([A-Za-z0-9 \-]+) + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 + - regex: \b(?:LGE[ \-]LG\-(?:AX|)|LGE |LGE?-LG|LGE?[ \-]|LG[ /\-]|lg[\-])([A-Za-z0-9]+)\b + device_replacement: LG $1 + brand_replacement: LG + model_replacement: $1 + - regex: (?:^LG[\-]?|^LGE[\-/]?)([A-Za-z]+[0-9]+[A-Za-z]*) + device_replacement: LG $1 + brand_replacement: LG + model_replacement: $1 + - regex: ^LG([0-9]+[A-Za-z]*) + device_replacement: LG $1 + brand_replacement: LG + model_replacement: $1 + - regex: (KIN\.[^ ]+) (\d+)\.(\d+) + device_replacement: Microsoft $1 + brand_replacement: Microsoft + model_replacement: $1 + - regex: (?:MSIE|XBMC).{0,200}\b(Xbox)\b + device_replacement: $1 + brand_replacement: Microsoft + model_replacement: $1 + - regex: ; ARM; Trident/6\.0; Touch[\);] + device_replacement: Microsoft Surface RT + brand_replacement: Microsoft + model_replacement: Surface RT + - regex: Motorola\-([A-Za-z0-9]+) + device_replacement: Motorola $1 + brand_replacement: Motorola + model_replacement: $1 + - regex: MOTO\-([A-Za-z0-9]+) + device_replacement: Motorola $1 + brand_replacement: Motorola + model_replacement: $1 + - regex: MOT\-([A-z0-9][A-z0-9\-]*) + device_replacement: Motorola $1 + brand_replacement: Motorola + model_replacement: $1 - regex: '; (moto[ a-zA-z0-9()]{0,50});((?: Build|.{0,50}\) AppleWebKit))' - device_replacement: '$1' - brand_replacement: 'Motorola' - model_replacement: '$1' + device_replacement: $1 + brand_replacement: Motorola + model_replacement: $1 - regex: '; {0,2}(moto)(.{0,50})(?: Build|\) AppleWebKit)' - device_replacement: 'Motorola$2' - brand_replacement: 'Motorola' - model_replacement: '$2' - - - ########## - # nintendo - ########## - - regex: 'Nintendo WiiU' - device_replacement: 'Nintendo Wii U' - brand_replacement: 'Nintendo' - model_replacement: 'Wii U' - - regex: 'Nintendo (DS|3DS|DSi|Wii);' - device_replacement: 'Nintendo $1' - brand_replacement: 'Nintendo' - model_replacement: '$1' - - ########## - # pantech - ########## - - regex: '(?:Pantech|PANTECH)[ _-]?([A-Za-z0-9\-]+)' - device_replacement: 'Pantech $1' - brand_replacement: 'Pantech' - model_replacement: '$1' - - ########## - # philips - ########## - - regex: 'Philips([A-Za-z0-9]+)' - device_replacement: 'Philips $1' - brand_replacement: 'Philips' - model_replacement: '$1' - - regex: 'Philips ([A-Za-z0-9]+)' - device_replacement: 'Philips $1' - brand_replacement: 'Philips' - model_replacement: '$1' - - ########## - # Samsung - ########## - # Samsung Smart-TV + device_replacement: Motorola$2 + brand_replacement: Motorola + model_replacement: $2 + - regex: Nintendo WiiU + device_replacement: Nintendo Wii U + brand_replacement: Nintendo + model_replacement: Wii U + - regex: Nintendo (Switch|DS|3DS|DSi|Wii); + device_replacement: Nintendo $1 + brand_replacement: Nintendo + model_replacement: $1 + - regex: (?:Pantech|PANTECH)[ _-]?([A-Za-z0-9\-]+) + device_replacement: Pantech $1 + brand_replacement: Pantech + model_replacement: $1 + - regex: Philips([A-Za-z0-9]+) + device_replacement: Philips $1 + brand_replacement: Philips + model_replacement: $1 + - regex: Philips ([A-Za-z0-9]+) + device_replacement: Philips $1 + brand_replacement: Philips + model_replacement: $1 - regex: '(SMART-TV); .{0,200} Tizen ' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - # Samsung Symbian Devices - - regex: 'SymbianOS/9\.\d.{0,200} Samsung[/\-]([A-Za-z0-9 \-]+)' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - regex: '(Samsung)(SGH)(i[0-9]+)' - device_replacement: '$1 $2$3' - brand_replacement: '$1' - model_replacement: '$2-$3' - - regex: 'SAMSUNG-ANDROID-MMS/([^;/]{1,100})' - device_replacement: '$1' - brand_replacement: 'Samsung' - model_replacement: '$1' - # Other Samsung - #- regex: 'SAMSUNG(?:; |-)([A-Za-z0-9\-]+)' - - regex: 'SAMSUNG(?:; |[ -/])([A-Za-z0-9\-]+)' - regex_flag: 'i' - device_replacement: 'Samsung $1' - brand_replacement: 'Samsung' - model_replacement: '$1' - - ########## - # Sega - ########## - - regex: '(Dreamcast)' - device_replacement: 'Sega $1' - brand_replacement: 'Sega' - model_replacement: '$1' - - ########## - # Siemens mobile - ########## - - regex: '^SIE-([A-Za-z0-9]+)' - device_replacement: 'Siemens $1' - brand_replacement: 'Siemens' - model_replacement: '$1' - - ########## - # Softbank - ########## - - regex: 'Softbank/[12]\.0/([A-Za-z0-9]+)' - device_replacement: 'Softbank $1' - brand_replacement: 'Softbank' - model_replacement: '$1' - - ########## - # SonyEricsson - ########## - - regex: 'SonyEricsson ?([A-Za-z0-9\-]+)' - device_replacement: 'Ericsson $1' - brand_replacement: 'SonyEricsson' - model_replacement: '$1' - - ########## - # Sony - ########## - - regex: 'Android [^;]{1,200}; ([^ ]+) (Sony)/' - device_replacement: '$2 $1' - brand_replacement: '$2' - model_replacement: '$1' - - regex: '(Sony)(?:BDP\/|\/|)([^ /;\)]+)[ /;\)]' - device_replacement: '$1 $2' - brand_replacement: '$1' - model_replacement: '$2' - - ######### - # Puffin Browser Device detect - # A=Android, I=iOS, P=Phone, T=Tablet - # AT=Android+Tablet - ######### - - regex: 'Puffin/[\d\.]+IT' - device_replacement: 'iPad' - brand_replacement: 'Apple' - model_replacement: 'iPad' - - regex: 'Puffin/[\d\.]+IP' - device_replacement: 'iPhone' - brand_replacement: 'Apple' - model_replacement: 'iPhone' - - regex: 'Puffin/[\d\.]+AT' - device_replacement: 'Generic Tablet' - brand_replacement: 'Generic' - model_replacement: 'Tablet' - - regex: 'Puffin/[\d\.]+AP' - device_replacement: 'Generic Smartphone' - brand_replacement: 'Generic' - model_replacement: 'Smartphone' - - ######### - # Android General Device Matching (far from perfect) - ######### - - regex: 'Android[\- ][\d]+\.[\d]+; [A-Za-z]{2}\-[A-Za-z]{0,2}; WOWMobile (.{1,200})( Build[/ ]|\))' - brand_replacement: 'Generic_Android' - model_replacement: '$1' - - regex: 'Android[\- ][\d]+\.[\d]+\-update1; [A-Za-z]{2}\-[A-Za-z]{0,2} {0,2}; {0,2}(.{1,200}?)( Build[/ ]|\))' - brand_replacement: 'Generic_Android' - model_replacement: '$1' - - regex: 'Android[\- ][\d]+(?:\.[\d]+)(?:\.[\d]+|); {0,2}[A-Za-z]{2}[_\-][A-Za-z]{0,2}\-? {0,2}; {0,2}(.{1,200}?)( Build[/ ]|\))' - brand_replacement: 'Generic_Android' - model_replacement: '$1' - - regex: 'Android[\- ][\d]+(?:\.[\d]+)(?:\.[\d]+|); {0,2}[A-Za-z]{0,2}\- {0,2}; {0,2}(.{1,200}?)( Build[/ ]|\))' - brand_replacement: 'Generic_Android' - model_replacement: '$1' - # No build info at all - "Build" follows locale immediately - - regex: 'Android[\- ][\d]+(?:\.[\d]+)(?:\.[\d]+|); {0,2}[a-z]{0,2}[_\-]?[A-Za-z]{0,2};?( Build[/ ]|\))' - device_replacement: 'Generic Smartphone' - brand_replacement: 'Generic' - model_replacement: 'Smartphone' - - regex: 'Android[\- ][\d]+(?:\.[\d]+)(?:\.[\d]+|); {0,3}\-?[A-Za-z]{2}; {0,2}(.{1,50}?)( Build[/ ]|\))' - brand_replacement: 'Generic_Android' - model_replacement: '$1' - - regex: 'Android \d+?(?:\.\d+|)(?:\.\d+|); ([^;]{1,100}?)(?: Build|\) AppleWebKit).{1,200}? Mobile Safari' - brand_replacement: 'Generic_Android' - model_replacement: '$1' - - regex: 'Android \d+?(?:\.\d+|)(?:\.\d+|); ([^;]{1,100}?)(?: Build|\) AppleWebKit).{1,200}? Safari' - brand_replacement: 'Generic_Android_Tablet' - model_replacement: '$1' + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: SymbianOS/9\.\d.{0,200} Samsung[/\-]([A-Za-z0-9 \-]+) + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: (Samsung)(SGH)(i[0-9]+) + device_replacement: $1 $2$3 + brand_replacement: $1 + model_replacement: $2-$3 + - regex: SAMSUNG-ANDROID-MMS/([^;/]{1,100}) + device_replacement: $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: SAMSUNG(?:; |[ -/])([A-Za-z0-9\-]+) + regex_flag: i + device_replacement: Samsung $1 + brand_replacement: Samsung + model_replacement: $1 + - regex: (Dreamcast) + device_replacement: Sega $1 + brand_replacement: Sega + model_replacement: $1 + - regex: ^SIE-([A-Za-z0-9]+) + device_replacement: Siemens $1 + brand_replacement: Siemens + model_replacement: $1 + - regex: Softbank/[12]\.0/([A-Za-z0-9]+) + device_replacement: Softbank $1 + brand_replacement: Softbank + model_replacement: $1 + - regex: SonyEricsson ?([A-Za-z0-9\-]+) + device_replacement: Ericsson $1 + brand_replacement: SonyEricsson + model_replacement: $1 + - regex: Android [^;]{1,200}; ([^ ]+) (Sony)/ + device_replacement: $2 $1 + brand_replacement: $2 + model_replacement: $1 + - regex: (Sony)(?:BDP\/|\/|)([^ /;\)]+)[ /;\)] + device_replacement: $1 $2 + brand_replacement: $1 + model_replacement: $2 + - regex: Puffin/[\d\.]+IT + device_replacement: iPad + brand_replacement: Apple + model_replacement: iPad + - regex: Puffin/[\d\.]+IP + device_replacement: iPhone + brand_replacement: Apple + model_replacement: iPhone + - regex: Puffin/[\d\.]+AT + device_replacement: Generic Tablet + brand_replacement: Generic + model_replacement: Tablet + - regex: Puffin/[\d\.]+AP + device_replacement: Generic Smartphone + brand_replacement: Generic + model_replacement: Smartphone + - regex: Android[\- ][\d]+\.[\d]+; [A-Za-z]{2}\-[A-Za-z]{0,2}; WOWMobile (.{1,200})( + Build[/ ]|\)) + brand_replacement: Generic_Android + model_replacement: $1 + - regex: Android[\- ][\d]+\.[\d]+\-update1; [A-Za-z]{2}\-[A-Za-z]{0,2} {0,2}; {0,2}(.{1,200}?)( + Build[/ ]|\)) + brand_replacement: Generic_Android + model_replacement: $1 + - regex: Android[\- ][\d]+(?:\.[\d]+)(?:\.[\d]+|); {0,2}[A-Za-z]{2}[_\-][A-Za-z]{0,2}\-? + {0,2}; {0,2}(.{1,200}?)( Build[/ ]|\)) + brand_replacement: Generic_Android + model_replacement: $1 + - regex: Android[\- ][\d]+(?:\.[\d]+)(?:\.[\d]+|); {0,2}[A-Za-z]{0,2}\- {0,2}; {0,2}(.{1,200}?)( + Build[/ ]|\)) + brand_replacement: Generic_Android + model_replacement: $1 + - regex: Android[\- ][\d]+(?:\.[\d]+)(?:\.[\d]+|); {0,2}[a-z]{0,2}[_\-]?[A-Za-z]{0,2};?( + Build[/ ]|\)) + device_replacement: Generic Smartphone + brand_replacement: Generic + model_replacement: Smartphone + - regex: Android[\- ][\d]+(?:\.[\d]+)(?:\.[\d]+|); {0,3}\-?[A-Za-z]{2}; {0,2}(.{1,50}?)( + Build[/ ]|\)) + brand_replacement: Generic_Android + model_replacement: $1 + - regex: 'Android \d+?(?:\.\d+|)(?:\.\d+|); ([^;]{1,100}?)(?: Build|\) AppleWebKit).{1,200}? + Mobile Safari' + brand_replacement: Generic_Android + model_replacement: $1 + - regex: 'Android \d+?(?:\.\d+|)(?:\.\d+|); ([^;]{1,100}?)(?: Build|\) AppleWebKit).{1,200}? + Safari' + brand_replacement: Generic_Android_Tablet + model_replacement: $1 - regex: 'Android \d+?(?:\.\d+|)(?:\.\d+|); ([^;]{1,100}?)(?: Build|\))' - brand_replacement: 'Generic_Android' - model_replacement: '$1' - - ########## - # Google TV - ########## - - regex: '(GoogleTV)' - brand_replacement: 'Generic_Inettv' - model_replacement: '$1' - - ########## - # WebTV - ########## - - regex: '(WebTV)/\d+.\d+' - brand_replacement: 'Generic_Inettv' - model_replacement: '$1' - # Roku Digital-Video-Players https://www.roku.com/ - - regex: '^(Roku)/DVP-\d+\.\d+' - brand_replacement: 'Generic_Inettv' - model_replacement: '$1' - - ########## - # Generic Tablet - ########## - - regex: '(Android 3\.\d|Opera Tablet|Tablet; .{1,100}Firefox/|Android.{0,100}(?:Tab|Pad))' - regex_flag: 'i' - device_replacement: 'Generic Tablet' - brand_replacement: 'Generic' - model_replacement: 'Tablet' - - ########## - # Generic Smart Phone - ########## - - regex: '(Symbian|\bS60(Version|V\d)|\bS60\b|\((Series 60|Windows Mobile|Palm OS|Bada); Opera Mini|Windows CE|Opera Mobi|BREW|Brew|Mobile; .{1,200}Firefox/|iPhone OS|Android|MobileSafari|Windows {0,2}Phone|\(webOS/|PalmOS)' - device_replacement: 'Generic Smartphone' - brand_replacement: 'Generic' - model_replacement: 'Smartphone' - - regex: '(hiptop|avantgo|plucker|xiino|blazer|elaine)' - regex_flag: 'i' - device_replacement: 'Generic Smartphone' - brand_replacement: 'Generic' - model_replacement: 'Smartphone' - - ########## - # Spiders (this is a hack...) - ########## - - regex: '^.{0,100}(bot|BUbiNG|zao|borg|DBot|oegp|silk|Xenu|zeal|^NING|CCBot|crawl|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|^Java/|^JNLP/|Daumoa|Daum|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|spider|msnbot|msrbot|vortex|^vortex|crawler|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|bingbot|BingPreview|openbot|gigabot|furlbot|polybot|seekbot|^voyager|archiver|Icarus6j|mogimogi|Netvibes|blitzbot|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|SeznamBot|ProoXiBot|wsr\-agent|Squrl Java|EtaoSpider|PaperLiBot|SputnikBot|A6\-Indexer|netresearch|searchsight|baiduspider|YisouSpider|ICC\-Crawler|http%20client|Python-urllib|dataparksearch|converacrawler|Screaming Frog|AppEngine-Google|YahooCacheSystem|fast\-webcrawler|Sogou Pic Spider|semanticdiscovery|Innovazion Crawler|facebookexternalhit|Google.{0,200}/\+/web/snippet|Google-HTTP-Java-Client|BlogBridge|IlTrovatore-Setaccio|InternetArchive|GomezAgent|WebThumbnail|heritrix|NewsGator|PagePeeker|Reaper|ZooShot|holmes|NL-Crawler|Pingdom|StatusCake|WhatsApp|masscan|Google Web Preview|Qwantify|Yeti|OgScrper)' - regex_flag: 'i' - device_replacement: 'Spider' - brand_replacement: 'Spider' - model_replacement: 'Desktop' - - ########## - # Generic Feature Phone - # take care to do case insensitive matching - ########## - - regex: '^(1207|3gso|4thp|501i|502i|503i|504i|505i|506i|6310|6590|770s|802s|a wa|acer|acs\-|airn|alav|asus|attw|au\-m|aur |aus |abac|acoo|aiko|alco|alca|amoi|anex|anny|anyw|aptu|arch|argo|bmobile|bell|bird|bw\-n|bw\-u|beck|benq|bilb|blac|c55/|cdm\-|chtm|capi|comp|cond|dall|dbte|dc\-s|dica|ds\-d|ds12|dait|devi|dmob|doco|dopo|dorado|el(?:38|39|48|49|50|55|58|68)|el[3456]\d{2}dual|erk0|esl8|ex300|ez40|ez60|ez70|ezos|ezze|elai|emul|eric|ezwa|fake|fly\-|fly_|g\-mo|g1 u|g560|gf\-5|grun|gene|go.w|good|grad|hcit|hd\-m|hd\-p|hd\-t|hei\-|hp i|hpip|hs\-c|htc |htc\-|htca|htcg)' - regex_flag: 'i' - device_replacement: 'Generic Feature Phone' - brand_replacement: 'Generic' - model_replacement: 'Feature Phone' - - regex: '^(htcp|htcs|htct|htc_|haie|hita|huaw|hutc|i\-20|i\-go|i\-ma|i\-mobile|i230|iac|iac\-|iac/|ig01|im1k|inno|iris|jata|kddi|kgt|kgt/|kpt |kwc\-|klon|lexi|lg g|lg\-a|lg\-b|lg\-c|lg\-d|lg\-f|lg\-g|lg\-k|lg\-l|lg\-m|lg\-o|lg\-p|lg\-s|lg\-t|lg\-u|lg\-w|lg/k|lg/l|lg/u|lg50|lg54|lge\-|lge/|leno|m1\-w|m3ga|m50/|maui|mc01|mc21|mcca|medi|meri|mio8|mioa|mo01|mo02|mode|modo|mot |mot\-|mt50|mtp1|mtv |mate|maxo|merc|mits|mobi|motv|mozz|n100|n101|n102|n202|n203|n300|n302|n500|n502|n505|n700|n701|n710|nec\-|nem\-|newg|neon)' - regex_flag: 'i' - device_replacement: 'Generic Feature Phone' - brand_replacement: 'Generic' - model_replacement: 'Feature Phone' - - regex: '^(netf|noki|nzph|o2 x|o2\-x|opwv|owg1|opti|oran|ot\-s|p800|pand|pg\-1|pg\-2|pg\-3|pg\-6|pg\-8|pg\-c|pg13|phil|pn\-2|pt\-g|palm|pana|pire|pock|pose|psio|qa\-a|qc\-2|qc\-3|qc\-5|qc\-7|qc07|qc12|qc21|qc32|qc60|qci\-|qwap|qtek|r380|r600|raks|rim9|rove|s55/|sage|sams|sc01|sch\-|scp\-|sdk/|se47|sec\-|sec0|sec1|semc|sgh\-|shar|sie\-|sk\-0|sl45|slid|smb3|smt5|sp01|sph\-|spv |spv\-|sy01|samm|sany|sava|scoo|send|siem|smar|smit|soft|sony|t\-mo|t218|t250|t600|t610|t618|tcl\-|tdg\-|telm|tim\-|ts70|tsm\-|tsm3|tsm5|tx\-9|tagt)' - regex_flag: 'i' - device_replacement: 'Generic Feature Phone' - brand_replacement: 'Generic' - model_replacement: 'Feature Phone' - - regex: '^(talk|teli|topl|tosh|up.b|upg1|utst|v400|v750|veri|vk\-v|vk40|vk50|vk52|vk53|vm40|vx98|virg|vertu|vite|voda|vulc|w3c |w3c\-|wapj|wapp|wapu|wapm|wig |wapi|wapr|wapv|wapy|wapa|waps|wapt|winc|winw|wonu|x700|xda2|xdag|yas\-|your|zte\-|zeto|aste|audi|avan|blaz|brew|brvw|bumb|ccwa|cell|cldc|cmd\-|dang|eml2|fetc|hipt|http|ibro|idea|ikom|ipaq|jbro|jemu|jigs|keji|kyoc|kyok|libw|m\-cr|midp|mmef|moto|mwbp|mywa|newt|nok6|o2im|pant|pdxg|play|pluc|port|prox|rozo|sama|seri|smal|symb|treo|upsi|vx52|vx53|vx60|vx61|vx70|vx80|vx81|vx83|vx85|wap\-|webc|whit|wmlb|xda\-|xda_)' - regex_flag: 'i' - device_replacement: 'Generic Feature Phone' - brand_replacement: 'Generic' - model_replacement: 'Feature Phone' - - regex: '^(Ice)$' - device_replacement: 'Generic Feature Phone' - brand_replacement: 'Generic' - model_replacement: 'Feature Phone' - - regex: '(wap[\-\ ]browser|maui|netfront|obigo|teleca|up\.browser|midp|Opera Mini)' - regex_flag: 'i' - device_replacement: 'Generic Feature Phone' - brand_replacement: 'Generic' - model_replacement: 'Feature Phone' - - ######### - # Apple - # @ref: https://www.apple.com/mac/ - # @note: lookup Mac OS, but exclude iPad, Apple TV, a HTC phone, Kindle, LG - # @note: put this at the end, since it is hard to implement contains foo, but not contain bar1, bar 2, bar 3 in go's re2 - ######### - - regex: 'Mac OS' - device_replacement: 'Mac' - brand_replacement: 'Apple' - model_replacement: 'Mac' + brand_replacement: Generic_Android + model_replacement: $1 + - regex: (GoogleTV) + brand_replacement: Generic_Inettv + model_replacement: $1 + - regex: (WebTV)/\d+.\d+ + brand_replacement: Generic_Inettv + model_replacement: $1 + - regex: ^(Roku)/DVP-\d+\.\d+ + brand_replacement: Generic_Inettv + model_replacement: $1 + - regex: (Android 3\.\d|Opera Tablet|Tablet; .{1,100}Firefox/|Android.{0,100}(?:Tab|Pad)) + regex_flag: i + device_replacement: Generic Tablet + brand_replacement: Generic + model_replacement: Tablet + - regex: (Symbian|\bS60(Version|V\d)|\bS60\b|\((Series 60|Windows Mobile|Palm OS|Bada); + Opera Mini|Windows CE|Opera Mobi|BREW|Brew|Mobile; .{1,200}Firefox/|iPhone OS|Android|MobileSafari|Windows + {0,2}Phone|\(webOS/|PalmOS) + device_replacement: Generic Smartphone + brand_replacement: Generic + model_replacement: Smartphone + - regex: (hiptop|avantgo|plucker|xiino|blazer|elaine) + regex_flag: i + device_replacement: Generic Smartphone + brand_replacement: Generic + model_replacement: Smartphone + - regex: ^.{0,100}(bot|BUbiNG|zao|borg|DBot|oegp|silk|Xenu|zeal|^NING|CCBot|crawl|htdig|lycos|slurp|teoma|voila|yahoo|Sogou|CiBra|Nutch|^Java/|^JNLP/|Daumoa|Daum|Genieo|ichiro|larbin|pompos|Scrapy|snappy|speedy|spider|msnbot|msrbot|vortex|^vortex|crawler|favicon|indexer|Riddler|scooter|scraper|scrubby|WhatWeb|WinHTTP|bingbot|BingPreview|openbot|gigabot|furlbot|polybot|seekbot|^voyager|archiver|Icarus6j|mogimogi|Netvibes|blitzbot|altavista|charlotte|findlinks|Retreiver|TLSProber|WordPress|SeznamBot|ProoXiBot|wsr\-agent|Squrl + Java|EtaoSpider|PaperLiBot|SputnikBot|A6\-Indexer|netresearch|searchsight|baiduspider|YisouSpider|ICC\-Crawler|http%20client|Python-urllib|dataparksearch|converacrawler|Screaming + Frog|AppEngine-Google|YahooCacheSystem|fast\-webcrawler|Sogou Pic Spider|semanticdiscovery|Innovazion + Crawler|facebookexternalhit|Google.{0,200}/\+/web/snippet|Google-HTTP-Java-Client|BlogBridge|IlTrovatore-Setaccio|InternetArchive|GomezAgent|WebThumbnail|heritrix|NewsGator|PagePeeker|Reaper|ZooShot|holmes|NL-Crawler|Pingdom|StatusCake|WhatsApp|masscan|Google + Web Preview|Qwantify|Yeti|OgScrper) + regex_flag: i + device_replacement: Spider + brand_replacement: Spider + model_replacement: Desktop + - regex: ^(1207|3gso|4thp|501i|502i|503i|504i|505i|506i|6310|6590|770s|802s|a wa|acer|acs\-|airn|alav|asus|attw|au\-m|aur + |aus |abac|acoo|aiko|alco|alca|amoi|anex|anny|anyw|aptu|arch|argo|bmobile|bell|bird|bw\-n|bw\-u|beck|benq|bilb|blac|c55/|cdm\-|chtm|capi|comp|cond|dall|dbte|dc\-s|dica|ds\-d|ds12|dait|devi|dmob|doco|dopo|dorado|el(?:38|39|48|49|50|55|58|68)|el[3456]\d{2}dual|erk0|esl8|ex300|ez40|ez60|ez70|ezos|ezze|elai|emul|eric|ezwa|fake|fly\-|fly_|g\-mo|g1 + u|g560|gf\-5|grun|gene|go.w|good|grad|hcit|hd\-m|hd\-p|hd\-t|hei\-|hp i|hpip|hs\-c|htc + |htc\-|htca|htcg) + regex_flag: i + device_replacement: Generic Feature Phone + brand_replacement: Generic + model_replacement: Feature Phone + - regex: ^(htcp|htcs|htct|htc_|haie|hita|huaw|hutc|i\-20|i\-go|i\-ma|i\-mobile|i230|iac|iac\-|iac/|ig01|im1k|inno|iris|jata|kddi|kgt|kgt/|kpt + |kwc\-|klon|lexi|lg g|lg\-a|lg\-b|lg\-c|lg\-d|lg\-f|lg\-g|lg\-k|lg\-l|lg\-m|lg\-o|lg\-p|lg\-s|lg\-t|lg\-u|lg\-w|lg/k|lg/l|lg/u|lg50|lg54|lge\-|lge/|leno|m1\-w|m3ga|m50/|maui|mc01|mc21|mcca|medi|meri|mio8|mioa|mo01|mo02|mode|modo|mot + |mot\-|mt50|mtp1|mtv |mate|maxo|merc|mits|mobi|motv|mozz|n100|n101|n102|n202|n203|n300|n302|n500|n502|n505|n700|n701|n710|nec\-|nem\-|newg|neon) + regex_flag: i + device_replacement: Generic Feature Phone + brand_replacement: Generic + model_replacement: Feature Phone + - regex: ^(netf|noki|nzph|o2 x|o2\-x|opwv|owg1|opti|oran|ot\-s|p800|pand|pg\-1|pg\-2|pg\-3|pg\-6|pg\-8|pg\-c|pg13|phil|pn\-2|pt\-g|palm|pana|pire|pock|pose|psio|qa\-a|qc\-2|qc\-3|qc\-5|qc\-7|qc07|qc12|qc21|qc32|qc60|qci\-|qwap|qtek|r380|r600|raks|rim9|rove|s55/|sage|sams|sc01|sch\-|scp\-|sdk/|se47|sec\-|sec0|sec1|semc|sgh\-|shar|sie\-|sk\-0|sl45|slid|smb3|smt5|sp01|sph\-|spv + |spv\-|sy01|samm|sany|sava|scoo|send|siem|smar|smit|soft|sony|t\-mo|t218|t250|t600|t610|t618|tcl\-|tdg\-|telm|tim\-|ts70|tsm\-|tsm3|tsm5|tx\-9|tagt) + regex_flag: i + device_replacement: Generic Feature Phone + brand_replacement: Generic + model_replacement: Feature Phone + - regex: ^(talk|teli|topl|tosh|up.b|upg1|utst|v400|v750|veri|vk\-v|vk40|vk50|vk52|vk53|vm40|vx98|virg|vertu|vite|voda|vulc|w3c + |w3c\-|wapj|wapp|wapu|wapm|wig |wapi|wapr|wapv|wapy|wapa|waps|wapt|winc|winw|wonu|x700|xda2|xdag|yas\-|your|zte\-|zeto|aste|audi|avan|blaz|brew|brvw|bumb|ccwa|cell|cldc|cmd\-|dang|eml2|fetc|hipt|http|ibro|idea|ikom|ipaq|jbro|jemu|jigs|keji|kyoc|kyok|libw|m\-cr|midp|mmef|moto|mwbp|mywa|newt|nok6|o2im|pant|pdxg|play|pluc|port|prox|rozo|sama|seri|smal|symb|treo|upsi|vx52|vx53|vx60|vx61|vx70|vx80|vx81|vx83|vx85|wap\-|webc|whit|wmlb|xda\-|xda_) + regex_flag: i + device_replacement: Generic Feature Phone + brand_replacement: Generic + model_replacement: Feature Phone + - regex: ^(Ice)$ + device_replacement: Generic Feature Phone + brand_replacement: Generic + model_replacement: Feature Phone + - regex: (wap[\-\ ]browser|maui|netfront|obigo|teleca|up\.browser|midp|Opera Mini) + regex_flag: i + device_replacement: Generic Feature Phone + brand_replacement: Generic + model_replacement: Feature Phone + - regex: Mac OS + device_replacement: Mac + brand_replacement: Apple + model_replacement: Mac diff --git a/web/.typesafe-i18n.json b/web/.typesafe-i18n.json index d744f47882..28148cbafb 100644 --- a/web/.typesafe-i18n.json +++ b/web/.typesafe-i18n.json @@ -1,5 +1,5 @@ { - "adapter": "react", - "$schema": "https://unpkg.com/typesafe-i18n@5.26.2/schema/typesafe-i18n.json", - "baseLocale": "en" + "adapter": "react", + "$schema": "https://unpkg.com/typesafe-i18n@5.26.2/schema/typesafe-i18n.json", + "baseLocale": "en" } diff --git a/web/.vscode/settings.json b/web/.vscode/settings.json index aaf48c1944..06ca10b994 100644 --- a/web/.vscode/settings.json +++ b/web/.vscode/settings.json @@ -1,10 +1,4 @@ { - "cSpell.words": [ - "MFATOTP", - "openidclient", - "TOTP", - "Typesafe", - "walletconnect" - ], - "typescript.tsdk": "node_modules/typescript/lib" + "cSpell.words": ["MFATOTP", "openidclient", "TOTP", "Typesafe", "walletconnect"], + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 80431c12c6..7ed2840502 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -1,342 +1,9155 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -dependencies: - '@floating-ui/react': - specifier: ^0.26.7 - version: 0.26.7(react-dom@18.2.0)(react@18.2.0) - '@github/webauthn-json': - specifier: ^2.1.1 - version: 2.1.1 - '@hookform/resolvers': - specifier: ^3.3.4 - version: 3.3.4(react-hook-form@7.49.3) - '@ladle/react': - specifier: ^4.0.2 - version: 4.0.2(@types/node@20.11.7)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0)(sass@1.70.0)(terser@5.27.0)(typescript@5.3.3) - '@metamask/detect-provider': - specifier: ^2.0.0 - version: 2.0.0 - '@react-rxjs/core': - specifier: ^0.10.7 - version: 0.10.7(react@18.2.0)(rxjs@7.8.1) - '@stablelib/base64': - specifier: ^1.0.1 - version: 1.0.1 - '@stablelib/x25519': - specifier: ^1.0.3 - version: 1.0.3 - '@tanstack/query-core': - specifier: ^4.32.6 - version: 4.36.1 - '@tanstack/react-query': - specifier: ^4.32.6 - version: 4.36.1(react-dom@18.2.0)(react@18.2.0) - '@tanstack/react-virtual': - specifier: 3.0.0-beta.9 - version: 3.0.0-beta.9(react@18.2.0) - '@tanstack/virtual-core': - specifier: 3.0.0-beta.9 - version: 3.0.0-beta.9 - '@tauri-apps/api': - specifier: ^1.5.3 - version: 1.5.3 - '@types/react-virtualized-auto-sizer': - specifier: ^1.0.4 - version: 1.0.4 - '@types/react-window': - specifier: ^1.8.8 - version: 1.8.8 - '@vitejs/plugin-legacy': - specifier: ^5.3.0 - version: 5.3.0(esbuild@0.19.12)(terser@5.27.0)(vite@5.0.12) - '@vitejs/plugin-react': - specifier: ^4.2.1 - version: 4.2.1(vite@5.0.12) - axios: - specifier: ^1.6.7 - version: 1.6.7 - byte-size: - specifier: ^8.1.1 - version: 8.1.1 - classnames: - specifier: ^2.5.1 - version: 2.5.1 - dayjs: - specifier: ^1.11.10 - version: 1.11.10 - detect-browser: - specifier: ^5.3.0 - version: 5.3.0 - ethers: - specifier: ^6.10.0 - version: 6.10.0 - events: - specifier: ^3.3.0 - version: 3.3.0 - fast-deep-equal: - specifier: ^3.1.3 - version: 3.1.3 - file-saver: - specifier: ^2.0.5 - version: 2.0.5 - framer-motion: - specifier: ^11.0.3 - version: 11.0.3(react-dom@18.2.0)(react@18.2.0) - get-text-width: - specifier: ^1.0.3 - version: 1.0.3 - hex-rgb: - specifier: ^5.0.0 - version: 5.0.0 - html-react-parser: - specifier: ^5.1.1 - version: 5.1.1(react@18.2.0) - itertools: - specifier: ^2.2.3 - version: 2.2.3 - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 - numbro: - specifier: ^2.4.0 - version: 2.4.0 - qrcode: - specifier: ^1.5.3 - version: 1.5.3 - radash: - specifier: ^11.0.0 - version: 11.0.0 - react: - specifier: ^18.2.0 - version: 18.2.0 - react-click-away-listener: - specifier: ^2.2.3 - version: 2.2.3(react-dom@18.2.0)(react@18.2.0) - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) - react-hook-form: - specifier: ^7.49.3 - version: 7.49.3(react@18.2.0) - react-idle-timer: - specifier: ^5.7.2 - version: 5.7.2(react-dom@18.2.0)(react@18.2.0) - react-is: - specifier: ^18.2.0 - version: 18.2.0 - react-loading-skeleton: - specifier: ^3.3.1 - version: 3.3.1(react@18.2.0) - react-markdown: - specifier: ^9.0.1 - version: 9.0.1(@types/react@18.2.48)(react@18.2.0) - react-qr-code: - specifier: ^2.0.12 - version: 2.0.12(react@18.2.0) - react-router: - specifier: ^6.21.3 - version: 6.21.3(react@18.2.0) - react-router-dom: - specifier: ^6.21.3 - version: 6.21.3(react-dom@18.2.0)(react@18.2.0) - react-virtualized-auto-sizer: - specifier: ^1.0.21 - version: 1.0.21(react-dom@18.2.0)(react@18.2.0) - react-window: - specifier: ^1.8.10 - version: 1.8.10(react-dom@18.2.0)(react@18.2.0) - recharts: - specifier: ^2.10.4 - version: 2.10.4(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) - rollup: - specifier: ^4.9.6 - version: 4.9.6 - rxjs: - specifier: ^7.8.1 - version: 7.8.1 - terser: - specifier: ^5.27.0 - version: 5.27.0 - typesafe-i18n: - specifier: ^5.26.2 - version: 5.26.2(typescript@5.3.3) - use-breakpoint: - specifier: ^4.0.1 - version: 4.0.1(react-dom@18.2.0)(react@18.2.0) - zod: - specifier: ^3.22.4 - version: 3.22.4 - zustand: - specifier: ^4.5.0 - version: 4.5.0(@types/react@18.2.48)(react@18.2.0) - -devDependencies: - '@babel/core': - specifier: ^7.23.9 - version: 7.23.9 - '@csstools/css-parser-algorithms': - specifier: ^2.5.0 - version: 2.5.0(@csstools/css-tokenizer@2.2.3) - '@csstools/css-tokenizer': - specifier: ^2.2.3 - version: 2.2.3 - '@hookform/devtools': - specifier: ^4.3.1 - version: 4.3.1(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0) - '@svgr/cli': - specifier: ^8.1.0 - version: 8.1.0(typescript@5.3.3) - '@tanstack/react-query-devtools': - specifier: ^4.32.6 - version: 4.36.1(@tanstack/react-query@4.36.1)(react-dom@18.2.0)(react@18.2.0) - '@types/byte-size': - specifier: ^8.1.2 - version: 8.1.2 - '@types/file-saver': - specifier: ^2.0.7 - version: 2.0.7 - '@types/lodash-es': - specifier: ^4.17.12 - version: 4.17.12 - '@types/node': - specifier: ^20.11.7 - version: 20.11.7 - '@types/react': - specifier: ^18.2.48 - version: 18.2.48 - '@types/react-dom': - specifier: ^18.2.18 - version: 18.2.18 - '@types/react-router-dom': - specifier: ^5.3.3 - version: 5.3.3 - '@typescript-eslint/eslint-plugin': - specifier: ^6.19.1 - version: 6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3) - '@typescript-eslint/parser': - specifier: ^6.19.1 - version: 6.19.1(eslint@8.56.0)(typescript@5.3.3) - '@vitejs/plugin-react-swc': - specifier: ^3.5.0 - version: 3.5.0(vite@5.0.12) - autoprefixer: - specifier: ^10.4.17 - version: 10.4.17(postcss@8.4.33) - concurrently: - specifier: ^8.2.2 - version: 8.2.2 - esbuild: - specifier: ^0.19.12 - version: 0.19.12 - eslint: - specifier: ^8.56.0 - version: 8.56.0 - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.0(eslint@8.56.0) - eslint-plugin-import: - specifier: ^2.29.1 - version: 2.29.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0) - eslint-plugin-jsx-a11y: - specifier: ^6.8.0 - version: 6.8.0(eslint@8.56.0) - eslint-plugin-prettier: - specifier: ^5.1.3 - version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.4) - eslint-plugin-react: - specifier: ^7.33.2 - version: 7.33.2(eslint@8.56.0) - eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.0(eslint@8.56.0) - eslint-plugin-react-refresh: - specifier: ^0.4.5 - version: 0.4.5(eslint@8.56.0) - eslint-plugin-simple-import-sort: - specifier: ^10.0.0 - version: 10.0.0(eslint@8.56.0) - postcss: - specifier: ^8.4.33 - version: 8.4.33 - prettier: - specifier: ^3.2.4 - version: 3.2.4 - prop-types: - specifier: ^15.8.1 - version: 15.8.1 - rollup-plugin-preserve-directives: - specifier: ^0.3.1 - version: 0.3.1(rollup@4.9.6) - sass: - specifier: ^1.70.0 - version: 1.70.0 - standard-version: - specifier: ^9.5.0 - version: 9.5.0 - stylelint-config-prettier-scss: - specifier: ^1.0.0 - version: 1.0.0(stylelint@16.2.0) - stylelint-config-standard-scss: - specifier: ^13.0.0 - version: 13.0.0(postcss@8.4.33)(stylelint@16.2.0) - stylelint-scss: - specifier: ^6.1.0 - version: 6.1.0(stylelint@16.2.0) - typedoc: - specifier: ^0.25.7 - version: 0.25.7(typescript@5.3.3) - typescript: - specifier: ~5.3.3 - version: 5.3.3 - typescript-eslint-language-service: - specifier: ^5.0.5 - version: 5.0.5(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3) - vite: - specifier: ^5.0.12 - version: 5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0) - vite-plugin-eslint: - specifier: ^1.8.1 - version: 1.8.1(eslint@8.56.0)(vite@5.0.12) - vite-plugin-package-version: - specifier: ^1.1.0 - version: 1.1.0(vite@5.0.12) +importers: + .: + dependencies: + '@floating-ui/react': + specifier: ^0.26.7 + version: 0.26.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@github/webauthn-json': + specifier: ^2.1.1 + version: 2.1.1 + '@hookform/resolvers': + specifier: ^3.3.4 + version: 3.3.4(react-hook-form@7.49.3(react@18.2.0)) + '@ladle/react': + specifier: ^4.0.2 + version: 4.0.2(@types/node@20.11.7)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.70.0)(terser@5.27.0)(typescript@5.3.3) + '@metamask/detect-provider': + specifier: ^2.0.0 + version: 2.0.0 + '@react-rxjs/core': + specifier: ^0.10.7 + version: 0.10.7(react@18.2.0)(rxjs@7.8.1) + '@stablelib/base64': + specifier: ^1.0.1 + version: 1.0.1 + '@stablelib/x25519': + specifier: ^1.0.3 + version: 1.0.3 + '@tanstack/query-core': + specifier: ^4.32.6 + version: 4.36.1 + '@tanstack/react-query': + specifier: ^4.32.6 + version: 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@tanstack/react-virtual': + specifier: 3.0.0-beta.9 + version: 3.0.0-beta.9(react@18.2.0) + '@tanstack/virtual-core': + specifier: 3.0.0-beta.9 + version: 3.0.0-beta.9 + '@tauri-apps/api': + specifier: ^1.5.3 + version: 1.5.3 + '@types/react-virtualized-auto-sizer': + specifier: ^1.0.4 + version: 1.0.4 + '@types/react-window': + specifier: ^1.8.8 + version: 1.8.8 + '@vitejs/plugin-legacy': + specifier: ^5.3.0 + version: 5.3.0(esbuild@0.19.12)(terser@5.27.0)(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)) + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.2.1(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)) + axios: + specifier: ^1.6.7 + version: 1.6.7 + byte-size: + specifier: ^8.1.1 + version: 8.1.1 + classnames: + specifier: ^2.5.1 + version: 2.5.1 + dayjs: + specifier: ^1.11.10 + version: 1.11.10 + detect-browser: + specifier: ^5.3.0 + version: 5.3.0 + ethers: + specifier: ^6.10.0 + version: 6.10.0 + events: + specifier: ^3.3.0 + version: 3.3.0 + fast-deep-equal: + specifier: ^3.1.3 + version: 3.1.3 + file-saver: + specifier: ^2.0.5 + version: 2.0.5 + framer-motion: + specifier: ^11.0.3 + version: 11.0.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + get-text-width: + specifier: ^1.0.3 + version: 1.0.3 + hex-rgb: + specifier: ^5.0.0 + version: 5.0.0 + html-react-parser: + specifier: ^5.1.1 + version: 5.1.1(react@18.2.0) + itertools: + specifier: ^2.2.3 + version: 2.2.3 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 + numbro: + specifier: ^2.4.0 + version: 2.4.0 + qrcode: + specifier: ^1.5.3 + version: 1.5.3 + radash: + specifier: ^11.0.0 + version: 11.0.0 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-click-away-listener: + specifier: ^2.2.3 + version: 2.2.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.49.3 + version: 7.49.3(react@18.2.0) + react-idle-timer: + specifier: ^5.7.2 + version: 5.7.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-is: + specifier: ^18.2.0 + version: 18.2.0 + react-loading-skeleton: + specifier: ^3.3.1 + version: 3.3.1(react@18.2.0) + react-markdown: + specifier: ^9.0.1 + version: 9.0.1(@types/react@18.2.48)(react@18.2.0) + react-qr-code: + specifier: ^2.0.12 + version: 2.0.12(react@18.2.0) + react-router: + specifier: ^6.21.3 + version: 6.21.3(react@18.2.0) + react-router-dom: + specifier: ^6.21.3 + version: 6.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-virtualized-auto-sizer: + specifier: ^1.0.21 + version: 1.0.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + react-window: + specifier: ^1.8.10 + version: 1.8.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + recharts: + specifier: ^2.10.4 + version: 2.10.4(prop-types@15.8.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + rollup: + specifier: ^4.9.6 + version: 4.9.6 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + terser: + specifier: ^5.27.0 + version: 5.27.0 + typesafe-i18n: + specifier: ^5.26.2 + version: 5.26.2(typescript@5.3.3) + use-breakpoint: + specifier: ^4.0.1 + version: 4.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + zod: + specifier: ^3.22.4 + version: 3.22.4 + zustand: + specifier: ^4.5.0 + version: 4.5.0(@types/react@18.2.48)(react@18.2.0) + devDependencies: + '@babel/core': + specifier: ^7.23.9 + version: 7.23.9 + '@csstools/css-parser-algorithms': + specifier: ^2.5.0 + version: 2.5.0(@csstools/css-tokenizer@2.2.3) + '@csstools/css-tokenizer': + specifier: ^2.2.3 + version: 2.2.3 + '@hookform/devtools': + specifier: ^4.3.1 + version: 4.3.1(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@svgr/cli': + specifier: ^8.1.0 + version: 8.1.0(typescript@5.3.3) + '@tanstack/react-query-devtools': + specifier: ^4.32.6 + version: 4.36.1(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@types/byte-size': + specifier: ^8.1.2 + version: 8.1.2 + '@types/file-saver': + specifier: ^2.0.7 + version: 2.0.7 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 + '@types/node': + specifier: ^20.11.7 + version: 20.11.7 + '@types/react': + specifier: ^18.2.48 + version: 18.2.48 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.2.18 + '@types/react-router-dom': + specifier: ^5.3.3 + version: 5.3.3 + '@typescript-eslint/eslint-plugin': + specifier: ^6.19.1 + version: 6.19.1(@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3) + '@typescript-eslint/parser': + specifier: ^6.19.1 + version: 6.19.1(eslint@8.56.0)(typescript@5.3.3) + '@vitejs/plugin-react-swc': + specifier: ^3.5.0 + version: 3.5.0(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)) + autoprefixer: + specifier: ^10.4.17 + version: 10.4.17(postcss@8.4.33) + concurrently: + specifier: ^8.2.2 + version: 8.2.2 + esbuild: + specifier: ^0.19.12 + version: 0.19.12 + eslint: + specifier: ^8.56.0 + version: 8.56.0 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.56.0) + eslint-plugin-import: + specifier: ^2.29.1 + version: 2.29.1(@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0) + eslint-plugin-jsx-a11y: + specifier: ^6.8.0 + version: 6.8.0(eslint@8.56.0) + eslint-plugin-prettier: + specifier: ^5.1.3 + version: 5.1.3(@types/eslint@8.56.2)(eslint-config-prettier@9.1.0(eslint@8.56.0))(eslint@8.56.0)(prettier@3.2.4) + eslint-plugin-react: + specifier: ^7.33.2 + version: 7.33.2(eslint@8.56.0) + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.0(eslint@8.56.0) + eslint-plugin-react-refresh: + specifier: ^0.4.5 + version: 0.4.5(eslint@8.56.0) + eslint-plugin-simple-import-sort: + specifier: ^10.0.0 + version: 10.0.0(eslint@8.56.0) + postcss: + specifier: ^8.4.33 + version: 8.4.33 + prettier: + specifier: ^3.2.4 + version: 3.2.4 + prop-types: + specifier: ^15.8.1 + version: 15.8.1 + rollup-plugin-preserve-directives: + specifier: ^0.3.1 + version: 0.3.1(rollup@4.9.6) + sass: + specifier: ^1.70.0 + version: 1.70.0 + standard-version: + specifier: ^9.5.0 + version: 9.5.0 + stylelint-config-prettier-scss: + specifier: ^1.0.0 + version: 1.0.0(stylelint@16.2.0(typescript@5.3.3)) + stylelint-config-standard-scss: + specifier: ^13.0.0 + version: 13.0.0(postcss@8.4.33)(stylelint@16.2.0(typescript@5.3.3)) + stylelint-scss: + specifier: ^6.1.0 + version: 6.1.0(stylelint@16.2.0(typescript@5.3.3)) + typedoc: + specifier: ^0.25.7 + version: 0.25.7(typescript@5.3.3) + typescript: + specifier: ~5.3.3 + version: 5.3.3 + typescript-eslint-language-service: + specifier: ^5.0.5 + version: 5.0.5(@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3) + vite: + specifier: ^5.0.12 + version: 5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0) + vite-plugin-eslint: + specifier: ^1.8.1 + version: 1.8.1(eslint@8.56.0)(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)) + vite-plugin-package-version: + specifier: ^1.1.0 + version: 1.1.0(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)) packages: + '@aashutoshrathi/word-wrap@1.2.6': + resolution: + { + integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==, + } + engines: { node: '>=0.10.0' } + + '@adraffy/ens-normalize@1.10.0': + resolution: + { + integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==, + } + + '@ampproject/remapping@2.2.1': + resolution: + { + integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==, + } + engines: { node: '>=6.0.0' } + + '@babel/code-frame@7.23.5': + resolution: + { + integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==, + } + engines: { node: '>=6.9.0' } + + '@babel/compat-data@7.23.5': + resolution: + { + integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==, + } + engines: { node: '>=6.9.0' } + + '@babel/core@7.23.9': + resolution: + { + integrity: sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==, + } + engines: { node: '>=6.9.0' } + + '@babel/generator@7.23.6': + resolution: + { + integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-annotate-as-pure@7.22.5': + resolution: + { + integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-builder-binary-assignment-operator-visitor@7.22.15': + resolution: + { + integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-compilation-targets@7.23.6': + resolution: + { + integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-create-class-features-plugin@7.23.7': + resolution: + { + integrity: sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.22.15': + resolution: + { + integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.4.4': + resolution: + { + integrity: sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-define-polyfill-provider@0.5.0': + resolution: + { + integrity: sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-environment-visitor@7.22.20': + resolution: + { + integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-function-name@7.23.0': + resolution: + { + integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-hoist-variables@7.22.5': + resolution: + { + integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-member-expression-to-functions@7.23.0': + resolution: + { + integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-imports@7.22.15': + resolution: + { + integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-transforms@7.23.3': + resolution: + { + integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.22.5': + resolution: + { + integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-plugin-utils@7.22.5': + resolution: + { + integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-remap-async-to-generator@7.22.20': + resolution: + { + integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.22.20': + resolution: + { + integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-simple-access@7.22.5': + resolution: + { + integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-skip-transparent-expression-wrappers@7.22.5': + resolution: + { + integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-split-export-declaration@7.22.6': + resolution: + { + integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-string-parser@7.23.4': + resolution: + { + integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-identifier@7.22.20': + resolution: + { + integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-option@7.23.5': + resolution: + { + integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-wrap-function@7.22.20': + resolution: + { + integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helpers@7.23.9': + resolution: + { + integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/highlight@7.23.4': + resolution: + { + integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==, + } + engines: { node: '>=6.9.0' } + + '@babel/parser@7.23.6': + resolution: + { + integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==, + } + engines: { node: '>=6.0.0' } + hasBin: true + + '@babel/parser@7.23.9': + resolution: + { + integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==, + } + engines: { node: '>=6.0.0' } + hasBin: true + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3': + resolution: + { + integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3': + resolution: + { + integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.7': + resolution: + { + integrity: sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: + { + integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: + { + integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: + { + integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: + { + integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: + { + integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-export-namespace-from@7.8.3': + resolution: + { + integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.23.3': + resolution: + { + integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.23.3': + resolution: + { + integrity: sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: + { + integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: + { + integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: + { + integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: + { + integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: + { + integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: + { + integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: + { + integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: + { + integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: + { + integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: + { + integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: + { + integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.23.3': + resolution: + { + integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.23.7': + resolution: + { + integrity: sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.23.3': + resolution: + { + integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.23.3': + resolution: + { + integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.23.4': + resolution: + { + integrity: sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.23.3': + resolution: + { + integrity: sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.23.4': + resolution: + { + integrity: sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.23.8': + resolution: + { + integrity: sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.23.3': + resolution: + { + integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.23.3': + resolution: + { + integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.23.3': + resolution: + { + integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.23.3': + resolution: + { + integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dynamic-import@7.23.4': + resolution: + { + integrity: sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.23.3': + resolution: + { + integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.23.4': + resolution: + { + integrity: sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.23.6': + resolution: + { + integrity: sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.23.3': + resolution: + { + integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.23.4': + resolution: + { + integrity: sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.23.3': + resolution: + { + integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.23.4': + resolution: + { + integrity: sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.23.3': + resolution: + { + integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.23.3': + resolution: + { + integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.23.3': + resolution: + { + integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.23.3': + resolution: + { + integrity: sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.23.3': + resolution: + { + integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.22.5': + resolution: + { + integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.23.3': + resolution: + { + integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.23.4': + resolution: + { + integrity: sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.23.4': + resolution: + { + integrity: sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.23.4': + resolution: + { + integrity: sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.23.3': + resolution: + { + integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.23.4': + resolution: + { + integrity: sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.23.4': + resolution: + { + integrity: sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.23.3': + resolution: + { + integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.23.3': + resolution: + { + integrity: sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.23.4': + resolution: + { + integrity: sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.23.3': + resolution: + { + integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.23.3': + resolution: + { + integrity: sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.23.3': + resolution: + { + integrity: sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.23.3': + resolution: + { + integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-reserved-words@7.23.3': + resolution: + { + integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.23.3': + resolution: + { + integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.23.3': + resolution: + { + integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.23.3': + resolution: + { + integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.23.3': + resolution: + { + integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.23.3': + resolution: + { + integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.23.3': + resolution: + { + integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.23.3': + resolution: + { + integrity: sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.23.3': + resolution: + { + integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.23.3': + resolution: + { + integrity: sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.23.8': + resolution: + { + integrity: sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: + { + integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/regjsgen@0.8.0': + resolution: + { + integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==, + } + + '@babel/runtime@7.23.8': + resolution: + { + integrity: sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==, + } + engines: { node: '>=6.9.0' } + + '@babel/template@7.22.15': + resolution: + { + integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==, + } + engines: { node: '>=6.9.0' } + + '@babel/template@7.23.9': + resolution: + { + integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==, + } + engines: { node: '>=6.9.0' } + + '@babel/traverse@7.23.7': + resolution: + { + integrity: sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==, + } + engines: { node: '>=6.9.0' } + + '@babel/traverse@7.23.9': + resolution: + { + integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==, + } + engines: { node: '>=6.9.0' } + + '@babel/types@7.23.6': + resolution: + { + integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==, + } + engines: { node: '>=6.9.0' } + + '@babel/types@7.23.9': + resolution: + { + integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==, + } + engines: { node: '>=6.9.0' } + + '@bundled-es-modules/cookie@2.0.0': + resolution: + { + integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==, + } + + '@bundled-es-modules/statuses@1.0.1': + resolution: + { + integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==, + } + + '@csstools/css-parser-algorithms@2.5.0': + resolution: + { + integrity: sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==, + } + engines: { node: ^14 || ^16 || >=18 } + peerDependencies: + '@csstools/css-tokenizer': ^2.2.3 + + '@csstools/css-tokenizer@2.2.3': + resolution: + { + integrity: sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==, + } + engines: { node: ^14 || ^16 || >=18 } + + '@csstools/media-query-list-parser@2.1.7': + resolution: + { + integrity: sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ==, + } + engines: { node: ^14 || ^16 || >=18 } + peerDependencies: + '@csstools/css-parser-algorithms': ^2.5.0 + '@csstools/css-tokenizer': ^2.2.3 + + '@csstools/selector-specificity@3.0.1': + resolution: + { + integrity: sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==, + } + engines: { node: ^14 || ^16 || >=18 } + peerDependencies: + postcss-selector-parser: ^6.0.13 + + '@emotion/babel-plugin@11.11.0': + resolution: + { + integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==, + } + + '@emotion/cache@11.11.0': + resolution: + { + integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==, + } + + '@emotion/hash@0.9.1': + resolution: + { + integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==, + } + + '@emotion/is-prop-valid@0.8.8': + resolution: + { + integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==, + } + + '@emotion/is-prop-valid@1.2.1': + resolution: + { + integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==, + } + + '@emotion/memoize@0.7.4': + resolution: + { + integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==, + } + + '@emotion/memoize@0.8.1': + resolution: + { + integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==, + } + + '@emotion/react@11.11.3': + resolution: + { + integrity: sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==, + } + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.1.3': + resolution: + { + integrity: sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==, + } + + '@emotion/sheet@1.2.2': + resolution: + { + integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==, + } + + '@emotion/styled@11.11.0': + resolution: + { + integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==, + } + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.8.1': + resolution: + { + integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==, + } + + '@emotion/use-insertion-effect-with-fallbacks@1.0.1': + resolution: + { + integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==, + } + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.2.1': + resolution: + { + integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==, + } + + '@emotion/weak-memoize@0.3.1': + resolution: + { + integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==, + } + + '@esbuild/aix-ppc64@0.19.12': + resolution: + { + integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==, + } + engines: { node: '>=12' } + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.19.12': + resolution: + { + integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.19.12': + resolution: + { + integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==, + } + engines: { node: '>=12' } + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.19.12': + resolution: + { + integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.19.12': + resolution: + { + integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.19.12': + resolution: + { + integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.19.12': + resolution: + { + integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.19.12': + resolution: + { + integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.19.12': + resolution: + { + integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.19.12': + resolution: + { + integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==, + } + engines: { node: '>=12' } + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.19.12': + resolution: + { + integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==, + } + engines: { node: '>=12' } + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.19.12': + resolution: + { + integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==, + } + engines: { node: '>=12' } + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.19.12': + resolution: + { + integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==, + } + engines: { node: '>=12' } + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.19.12': + resolution: + { + integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==, + } + engines: { node: '>=12' } + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.19.12': + resolution: + { + integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==, + } + engines: { node: '>=12' } + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.19.12': + resolution: + { + integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==, + } + engines: { node: '>=12' } + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.19.12': + resolution: + { + integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.19.12': + resolution: + { + integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.19.12': + resolution: + { + integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.19.12': + resolution: + { + integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.19.12': + resolution: + { + integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==, + } + engines: { node: '>=12' } + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.19.12': + resolution: + { + integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==, + } + engines: { node: '>=12' } + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.19.12': + resolution: + { + integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==, + } + engines: { node: '>=12' } + cpu: [x64] + os: [win32] + + '@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.0': + resolution: + { + integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==, + } + 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.56.0': + resolution: + { + integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + '@floating-ui/core@1.6.0': + resolution: + { + integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==, + } + + '@floating-ui/dom@1.6.0': + resolution: + { + integrity: sha512-SZ0BEXzsaaS6THZfZJUcAobbZTD+MvfGM42bxgeg0Tnkp4/an/avqwAXiVLsFtIBZtfsx3Ymvwx0+KnnhdA/9g==, + } + + '@floating-ui/react-dom@2.0.7': + resolution: + { + integrity: sha512-B5GJxKUyPcGsvE1vua+Abvw0t6zVMyTbtG+Jk7BoI4hfc5Ahv50dstRIAn0nS0274kR9gnKwxIXyGA8EzBZJrA==, + } + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.26.7': + resolution: + { + integrity: sha512-0uMI9IBJBPPt8N+8uRg4gazJvQReWTu/fVUHHLfAOuy1WB6f242jtjWm52hLJG8nzuZVuU+2crW4lJbJQoqeIA==, + } + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.1': + resolution: + { + integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==, + } + + '@github/webauthn-json@2.1.1': + resolution: + { + integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==, + } + hasBin: true + + '@hookform/devtools@4.3.1': + resolution: + { + integrity: sha512-CrWxEoHQZaOXJZVQ8KBgOuAa8p2LI8M0DAN5GTRTmdCieRwFVjVDEmuTAVazWVRRkpEQSgSt3KYp7VmmqXdEnw==, + } + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + + '@hookform/resolvers@3.3.4': + resolution: + { + integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==, + } + peerDependencies: + react-hook-form: ^7.0.0 + + '@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 + + '@hutson/parse-repository-url@3.0.2': + resolution: + { + integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==, + } + engines: { node: '>=6.9.0' } + + '@isaacs/cliui@8.0.2': + resolution: + { + integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, + } + engines: { node: '>=12' } + + '@jridgewell/gen-mapping@0.3.3': + resolution: + { + integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==, + } + engines: { node: '>=6.0.0' } + + '@jridgewell/resolve-uri@3.1.1': + resolution: + { + integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==, + } + engines: { node: '>=6.0.0' } + + '@jridgewell/set-array@1.1.2': + resolution: + { + integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==, + } + engines: { node: '>=6.0.0' } + + '@jridgewell/source-map@0.3.5': + resolution: + { + integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==, + } + + '@jridgewell/sourcemap-codec@1.4.15': + resolution: + { + integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==, + } + + '@jridgewell/trace-mapping@0.3.22': + resolution: + { + integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==, + } + + '@ladle/react-context@1.0.1': + resolution: + { + integrity: sha512-xVQ8siyOEQG6e4Knibes1uA3PTyXnqiMmfSmd5pIbkzeDty8NCBtYHhTXSlfmcDNEsw/G8OzNWo4VbyQAVDl2A==, + } + peerDependencies: + react: '>=16.14.0' + react-dom: '>=16.14.0' + + '@ladle/react@4.0.2': + resolution: + { + integrity: sha512-SnYniR/U7kJX1Zh199jhjxqiFa5e5eA8chuX6uYEZYAUtCCY/hQqGr7/7Grr0j6Q4FYu9iQyyV2K1NJKDdUZIw==, + } + engines: { node: '>=18.0.0' } + hasBin: true + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@mdx-js/mdx@3.0.0': + resolution: + { + integrity: sha512-Icm0TBKBLYqroYbNW3BPnzMGn+7mwpQOK310aZ7+fkCtiU3aqv2cdcX+nd0Ydo3wI5Rx8bX2Z2QmGb/XcAClCw==, + } + + '@mdx-js/react@3.0.0': + resolution: + { + integrity: sha512-nDctevR9KyYFyV+m+/+S4cpzCWHqj+iHDHq3QrsWezcC+B17uZdIWgCguESUkwFhM3n/56KxWVE3V6EokrmONQ==, + } + peerDependencies: + '@types/react': '>=16' + react: '>=16' + + '@metamask/detect-provider@2.0.0': + resolution: + { + integrity: sha512-sFpN+TX13E9fdBDh9lvQeZdJn4qYoRb/6QF2oZZK/Pn559IhCFacPMU1rMuqyXoFQF3JSJfii2l98B87QDPeCQ==, + } + engines: { node: '>=14.0.0' } + + '@mswjs/cookies@1.1.0': + resolution: + { + integrity: sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==, + } + engines: { node: '>=18' } + + '@mswjs/interceptors@0.25.14': + resolution: + { + integrity: sha512-2dnIxl+obqIqjoPXTFldhe6pcdOrqiz+GcLaQQ6hmL02OldAF7nIC+rUgTWm+iF6lvmyCVhFFqbgbapNhR8eag==, + } + engines: { node: '>=18' } + + '@noble/curves@1.2.0': + resolution: + { + integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==, + } + + '@noble/hashes@1.3.2': + resolution: + { + integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==, + } + engines: { node: '>= 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' } + + '@open-draft/deferred-promise@2.2.0': + resolution: + { + integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==, + } + + '@open-draft/logger@0.3.0': + resolution: + { + integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==, + } + + '@open-draft/until@2.1.0': + resolution: + { + integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==, + } + + '@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 } + + '@reach/observe-rect@1.2.0': + resolution: + { + integrity: sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==, + } + + '@react-rxjs/core@0.10.7': + resolution: + { + integrity: sha512-dornp8pUs9OcdqFKKRh9+I2FVe21gWufNun6RYU1ddts7kUy9i4Thvl0iqcPFbGY61cJQMAJF7dxixWMSD/A/A==, + } + peerDependencies: + react: '>=16.8.0' + rxjs: '>=7' + + '@remix-run/router@1.14.2': + resolution: + { + integrity: sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==, + } + engines: { node: '>=14.0.0' } + + '@rollup/pluginutils@4.2.1': + resolution: + { + integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==, + } + engines: { node: '>= 8.0.0' } + + '@rollup/rollup-android-arm-eabi@4.9.6': + resolution: + { + integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==, + } + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.9.6': + resolution: + { + integrity: sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==, + } + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.9.6': + resolution: + { + integrity: sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==, + } + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.9.6': + resolution: + { + integrity: sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==, + } + cpu: [x64] + os: [darwin] + + '@rollup/rollup-linux-arm-gnueabihf@4.9.6': + resolution: + { + integrity: sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==, + } + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.9.6': + resolution: + { + integrity: sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==, + } + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.9.6': + resolution: + { + integrity: sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==, + } + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.9.6': + resolution: + { + integrity: sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==, + } + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.9.6': + resolution: + { + integrity: sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==, + } + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.9.6': + resolution: + { + integrity: sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==, + } + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.9.6': + resolution: + { + integrity: sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==, + } + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.9.6': + resolution: + { + integrity: sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==, + } + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.9.6': + resolution: + { + integrity: sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==, + } + cpu: [x64] + os: [win32] + + '@rx-state/core@0.1.4': + resolution: + { + integrity: sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ==, + } + peerDependencies: + rxjs: '>=7' + + '@sindresorhus/merge-streams@1.0.0': + resolution: + { + integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==, + } + engines: { node: '>=18' } + + '@stablelib/base64@1.0.1': + resolution: + { + integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==, + } + + '@stablelib/binary@1.0.1': + resolution: + { + integrity: sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==, + } + + '@stablelib/bytes@1.0.1': + resolution: + { + integrity: sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ==, + } + + '@stablelib/int@1.0.1': + resolution: + { + integrity: sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==, + } + + '@stablelib/keyagreement@1.0.1': + resolution: + { + integrity: sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg==, + } + + '@stablelib/random@1.0.2': + resolution: + { + integrity: sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==, + } + + '@stablelib/wipe@1.0.1': + resolution: + { + integrity: sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==, + } + + '@stablelib/x25519@1.0.3': + resolution: + { + integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==, + } + + '@svgr/babel-plugin-add-jsx-attribute@8.0.0': + resolution: + { + integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==, + } + engines: { node: '>=14' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0': + resolution: + { + integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==, + } + engines: { node: '>=14' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0': + resolution: + { + integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==, + } + engines: { node: '>=14' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0': + resolution: + { + integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==, + } + engines: { node: '>=14' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-dynamic-title@8.0.0': + resolution: + { + integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==, + } + engines: { node: '>=14' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-svg-em-dimensions@8.0.0': + resolution: + { + integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==, + } + engines: { node: '>=14' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-react-native-svg@8.1.0': + resolution: + { + integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==, + } + engines: { node: '>=14' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-plugin-transform-svg-component@8.0.0': + resolution: + { + integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==, + } + engines: { node: '>=12' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/babel-preset@8.1.0': + resolution: + { + integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==, + } + engines: { node: '>=14' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@svgr/cli@8.1.0': + resolution: + { + integrity: sha512-SnlaLspB610XFXvs3PmhzViHErsXp0yIy4ERyZlHDlO1ro2iYtHMWYk2mztdLD/lBjiA4ZXe4RePON3qU/Tc4A==, + } + engines: { node: '>=14' } + hasBin: true + + '@svgr/core@8.1.0': + resolution: + { + integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==, + } + engines: { node: '>=14' } + + '@svgr/hast-util-to-babel-ast@8.0.0': + resolution: + { + integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==, + } + engines: { node: '>=14' } + + '@svgr/plugin-jsx@8.1.0': + resolution: + { + integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==, + } + engines: { node: '>=14' } + peerDependencies: + '@svgr/core': '*' + + '@svgr/plugin-prettier@8.1.0': + resolution: + { + integrity: sha512-o4/uFI8G64tAjBZ4E7gJfH+VP7Qi3T0+M4WnIsP91iFnGPqs5WvPDkpZALXPiyWEtzfYs1Rmwy1Zdfu8qoZuKw==, + } + engines: { node: '>=14' } + peerDependencies: + '@svgr/core': '*' + + '@svgr/plugin-svgo@8.1.0': + resolution: + { + integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==, + } + engines: { node: '>=14' } + peerDependencies: + '@svgr/core': '*' + + '@swc/core-darwin-arm64@1.3.106': + resolution: + { + integrity: sha512-XYcbViNyHnnm7RWOAO1YipMmthM7m2aXF32b0y+JMLYFBEyFpjVX9btLkzeL7wRx/5B3I35yJNhE+xyx0Q1Gkw==, + } + engines: { node: '>=10' } + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.3.106': + resolution: + { + integrity: sha512-YKDPhUdfuwhmOUS9+CaIwl/0Tp+f1b73BH2EIESuxSNsogZf18a8HQ8O0fQEwdiwmA5LEqw47cj+kfOWV/0+kw==, + } + engines: { node: '>=10' } + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.3.106': + resolution: + { + integrity: sha512-bHxxJXogvFfocLL5inZxxtx/x/WgKozigp80Vbx0viac1fPDJrqKBw2X4MzpMiuTRAGVQ03jJI6pDwbSBf+yDw==, + } + engines: { node: '>=10' } + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.3.106': + resolution: + { + integrity: sha512-c7jue++CHLgtpeaakEukoCLT9eNrImizbleE9Y7Is8CHqLq/7DG4s+7ma9DFKXIzW2MpTg9byIEQfpqSphVW6A==, + } + engines: { node: '>=10' } + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.3.106': + resolution: + { + integrity: sha512-51EaC3Q8qAhLtWVnAVqoYX/gk3tK31cCBzUpwCcmhianhEBM2/WtKRAS4MqPhE8VVZuN3WjO2c2JaF2mX0yuoA==, + } + engines: { node: '>=10' } + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.3.106': + resolution: + { + integrity: sha512-tOUi8BB6jAeCXgx7ESLNnX7nrbMVKQ/XajK77v7Ad4SXf9HYArnimBJpXUUyVFJTXLSv4e6c7s6XHHqXb5Lwcg==, + } + engines: { node: '>=10' } + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.3.106': + resolution: + { + integrity: sha512-binLw4Lbd83NPy4/m/teH2nbaifxveSD+sKDvpxywRbvYW2I0w/iCBpUBcbnl16TQF4TPOGpq5YwG9lVxPVw5g==, + } + engines: { node: '>=10' } + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.3.106': + resolution: + { + integrity: sha512-n4ttBWr8tM7DPzwcEOIBTyTMHZTzCmbic/HTtxEsPyMAf/Daen+yrTKzjPP6k2usfSrjkxA780RSJJxI1N8r2w==, + } + engines: { node: '>=10' } + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.3.106': + resolution: + { + integrity: sha512-GhDNIwxE5FhkujESI6h/4ysT3wxwmrzTUlZYaR8rRui6a6SdX9feIPUHPEE5o5hpyp+xqlmvRxKkRxOnwsq8iA==, + } + engines: { node: '>=10' } + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.3.106': + resolution: + { + integrity: sha512-2M6yWChuMS1+/MPo3Dor0SOMkvmiugonWlzsZBAu/oZboH2xKrHSRv7brsBujb2Oe47r+NsbV+vq9tnnP9Vl1Q==, + } + engines: { node: '>=10' } + cpu: [x64] + os: [win32] + + '@swc/core@1.3.106': + resolution: + { + integrity: sha512-++QPSPkFq2qELYVScxNHJC42hKQChjiTWS2P0QQ5JWT4NHb9lmNSfrc1ylFIyImwRnxsW2MTBALLYLf95EFAsg==, + } + engines: { node: '>=10' } + peerDependencies: + '@swc/helpers': ^0.5.0 + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.2': + resolution: + { + integrity: sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==, + } + + '@swc/types@0.1.5': + resolution: + { + integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==, + } + + '@tanstack/match-sorter-utils@8.11.7': + resolution: + { + integrity: sha512-4PUKgaaFpiB7MK406N5VAiLu2VUhDumojGWhEC8kNQ767RGU2vsJDI7Xp4D8lMBzijqswRWz3U8ioa2zUKnFeQ==, + } + engines: { node: '>=12' } + + '@tanstack/query-core@4.36.1': + resolution: + { + integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==, + } + + '@tanstack/react-query-devtools@4.36.1': + resolution: + { + integrity: sha512-WYku83CKP3OevnYSG8Y/QO9g0rT75v1om5IvcWUwiUZJ4LanYGLVCZ8TdFG5jfsq4Ej/lu2wwDAULEUnRIMBSw==, + } + peerDependencies: + '@tanstack/react-query': ^4.36.1 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@tanstack/react-query@4.36.1': + resolution: + { + integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==, + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + + '@tanstack/react-virtual@3.0.0-beta.9': + resolution: + { + integrity: sha512-zW914lzyPWunqKaHYwiOu/R7k/LJ43W6lutpCAeDbYqaOOme5tp/G+/PCpnK2vKseUnCpKM7x/WicrQoDX8H0A==, + } + engines: { node: '>=12' } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + '@tanstack/virtual-core@3.0.0-beta.9': + resolution: + { + integrity: sha512-Aaku1DMatF8GNbAZj2ahGN9F3l6KLe2n1jzgAUO8IT07D1spoZ9jCeazOQ7gvCIIEZjceuL9klx08MnsFJdCqg==, + } + engines: { node: '>=12' } + + '@tauri-apps/api@1.5.3': + resolution: + { + integrity: sha512-zxnDjHHKjOsrIzZm6nO5Xapb/BxqUq1tc7cGkFXsFkGTsSWgCPH1D8mm0XS9weJY2OaR73I3k3S+b7eSzJDfqA==, + } + engines: { node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1' } + + '@trysound/sax@0.2.0': + resolution: + { + integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==, + } + engines: { node: '>=10.13.0' } + + '@types/acorn@4.0.6': + resolution: + { + integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==, + } + + '@types/babel__core@7.20.5': + resolution: + { + integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, + } + + '@types/babel__generator@7.6.8': + resolution: + { + integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==, + } + + '@types/babel__template@7.4.4': + resolution: + { + integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, + } + + '@types/babel__traverse@7.20.5': + resolution: + { + integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==, + } + + '@types/byte-size@8.1.2': + resolution: + { + integrity: sha512-jGyVzYu6avI8yuqQCNTZd65tzI8HZrLjKX9sdMqZrGWVlNChu0rf6p368oVEDCYJe5BMx2Ov04tD1wqtgTwGSA==, + } + + '@types/cookie@0.6.0': + resolution: + { + integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==, + } + + '@types/d3-array@3.2.1': + resolution: + { + integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==, + } + + '@types/d3-color@3.1.3': + resolution: + { + integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==, + } + + '@types/d3-ease@3.0.2': + resolution: + { + integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==, + } + + '@types/d3-interpolate@3.0.4': + resolution: + { + integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==, + } + + '@types/d3-path@3.0.2': + resolution: + { + integrity: sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==, + } + + '@types/d3-scale@4.0.8': + resolution: + { + integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==, + } + + '@types/d3-shape@3.1.6': + resolution: + { + integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==, + } + + '@types/d3-time@3.0.3': + resolution: + { + integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==, + } + + '@types/d3-timer@3.0.2': + resolution: + { + integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==, + } + + '@types/debug@4.1.12': + resolution: + { + integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==, + } + + '@types/eslint@8.56.2': + resolution: + { + integrity: sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==, + } + + '@types/estree-jsx@1.0.3': + resolution: + { + integrity: sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==, + } + + '@types/estree@1.0.5': + resolution: + { + integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==, + } + + '@types/file-saver@2.0.7': + resolution: + { + integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==, + } + + '@types/hast@3.0.3': + resolution: + { + integrity: sha512-2fYGlaDy/qyLlhidX42wAH0KBi2TCjKMH8CHmBXgRlJ3Y+OXTiqsPQ6IWarZKwF1JoUcAJdPogv1d4b0COTpmQ==, + } + + '@types/history@4.7.11': + resolution: + { + integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==, + } + + '@types/json-schema@7.0.15': + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + '@types/json5@0.0.29': + resolution: + { + integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==, + } + + '@types/lodash-es@4.17.12': + resolution: + { + integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==, + } + + '@types/lodash@4.14.202': + resolution: + { + integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==, + } + + '@types/mdast@4.0.3': + resolution: + { + integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==, + } + + '@types/mdx@2.0.10': + resolution: + { + integrity: sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==, + } + + '@types/minimist@1.2.5': + resolution: + { + integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==, + } + + '@types/ms@0.7.34': + resolution: + { + integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==, + } + + '@types/node@18.15.13': + resolution: + { + integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==, + } + + '@types/node@20.11.7': + resolution: + { + integrity: sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==, + } + + '@types/normalize-package-data@2.4.4': + resolution: + { + integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==, + } + + '@types/parse-json@4.0.2': + resolution: + { + integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==, + } + + '@types/prismjs@1.26.3': + resolution: + { + integrity: sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==, + } + + '@types/prop-types@15.7.11': + resolution: + { + integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==, + } + + '@types/react-dom@18.2.18': + resolution: + { + integrity: sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==, + } + + '@types/react-router-dom@5.3.3': + resolution: + { + integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==, + } + + '@types/react-router@5.1.20': + resolution: + { + integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==, + } + + '@types/react-virtualized-auto-sizer@1.0.4': + resolution: + { + integrity: sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==, + } + + '@types/react-window@1.8.8': + resolution: + { + integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==, + } + + '@types/react@18.2.48': + resolution: + { + integrity: sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==, + } + + '@types/scheduler@0.16.8': + resolution: + { + integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==, + } + + '@types/semver@7.5.6': + resolution: + { + integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==, + } + + '@types/statuses@2.0.4': + resolution: + { + integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==, + } + + '@types/unist@2.0.10': + resolution: + { + integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==, + } + + '@types/unist@3.0.2': + resolution: + { + integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==, + } + + '@typescript-eslint/eslint-plugin@6.19.1': + resolution: + { + integrity: sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.19.1': + resolution: + { + integrity: sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.19.1': + resolution: + { + integrity: sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + + '@typescript-eslint/type-utils@6.19.1': + resolution: + { + integrity: sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.19.1': + resolution: + { + integrity: sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + + '@typescript-eslint/typescript-estree@6.19.1': + resolution: + { + integrity: sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.19.1': + resolution: + { + integrity: sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.19.1': + resolution: + { + integrity: sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==, + } + engines: { node: ^16.0.0 || >=18.0.0 } + + '@ungap/structured-clone@1.2.0': + resolution: + { + integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==, + } + + '@vitejs/plugin-legacy@5.3.0': + resolution: + { + integrity: sha512-BhW+WcJmEgW5G/1UQRiVQ7wz9/ZPnxqzExT9n0zAk4RlqQQ/26udIeXzdU8+03AGnaF61wmZlCspexgEnxFWMA==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + peerDependencies: + terser: ^5.4.0 + vite: ^5.0.0 + + '@vitejs/plugin-react-swc@3.5.0': + resolution: + { + integrity: sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==, + } + peerDependencies: + vite: ^4 || ^5 + + '@vitejs/plugin-react@4.2.1': + resolution: + { + integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + vite: ^4.2.0 || ^5.0.0 + + JSONStream@1.3.5: + resolution: + { + integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==, + } + hasBin: true + + accepts@1.3.8: + resolution: + { + integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==, + } + engines: { node: '>= 0.6' } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.11.3: + resolution: + { + integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==, + } + engines: { node: '>=0.4.0' } + hasBin: true + + add-stream@1.0.0: + resolution: + { + integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==, + } + + aes-js@4.0.0-beta.5: + resolution: + { + integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==, + } + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, + } + + ajv@8.12.0: + resolution: + { + integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==, + } + + ansi-align@3.0.1: + resolution: + { + integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==, + } + + ansi-escapes@4.3.2: + resolution: + { + integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==, + } + engines: { node: '>=8' } + + 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-sequence-parser@1.1.1: + resolution: + { + integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==, + } + + 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' } + + anymatch@3.1.3: + resolution: + { + integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, + } + engines: { node: '>= 8' } + + 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.0: + resolution: + { + integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==, + } + + array-ify@1.0.0: + resolution: + { + integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==, + } + + 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.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.tosorted@1.1.2: + resolution: + { + integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==, + } + + arraybuffer.prototype.slice@1.0.2: + resolution: + { + integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==, + } + engines: { node: '>= 0.4' } + + arrify@1.0.1: + resolution: + { + integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==, + } + engines: { node: '>=0.10.0' } + + ast-types-flow@0.0.8: + resolution: + { + integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==, + } + + astral-regex@2.0.0: + resolution: + { + integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==, + } + engines: { node: '>=8' } + + astring@1.8.6: + resolution: + { + integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==, + } + hasBin: true + + asynciterator.prototype@1.0.0: + resolution: + { + integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==, + } + + asynckit@0.4.0: + resolution: + { + integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, + } + + autoprefixer@10.4.17: + resolution: + { + integrity: sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==, + } + engines: { node: ^10 || ^12 || >=14 } + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.5: + resolution: + { + integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==, + } + engines: { node: '>= 0.4' } + + axe-core@4.7.0: + resolution: + { + integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==, + } + engines: { node: '>=4' } + + axe-core@4.8.3: + resolution: + { + integrity: sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw==, + } + engines: { node: '>=4' } + + axios@1.6.7: + resolution: + { + integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==, + } + + axobject-query@3.2.1: + resolution: + { + integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==, + } + + babel-plugin-macros@3.1.0: + resolution: + { + integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==, + } + engines: { node: '>=10', npm: '>=6' } + + babel-plugin-polyfill-corejs2@0.4.8: + resolution: + { + integrity: sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.8.7: + resolution: + { + integrity: sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.5.5: + resolution: + { + integrity: sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + bail@2.0.2: + resolution: + { + integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==, + } + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + balanced-match@2.0.0: + resolution: + { + integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==, + } + + base64-js@1.5.1: + resolution: + { + integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, + } + + bcp-47-match@2.0.3: + resolution: + { + integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==, + } + + big-integer@1.6.52: + resolution: + { + integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==, + } + engines: { node: '>=0.6' } + + bignumber.js@9.1.2: + resolution: + { + integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==, + } + + binary-extensions@2.2.0: + resolution: + { + integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==, + } + engines: { node: '>=8' } + + bl@4.1.0: + resolution: + { + integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==, + } + + boolbase@1.0.0: + resolution: + { + integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, + } + + boxen@7.1.1: + resolution: + { + integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==, + } + engines: { node: '>=14.16' } + + bplist-parser@0.2.0: + resolution: + { + integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==, + } + engines: { node: '>= 5.10.0' } + + 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.22.2: + resolution: + { + integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==, + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + buffer-from@1.1.2: + resolution: + { + integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, + } + + buffer@5.7.1: + resolution: + { + integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, + } + + bundle-name@3.0.0: + resolution: + { + integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==, + } + engines: { node: '>=12' } + + byte-size@8.1.1: + resolution: + { + integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==, + } + engines: { node: '>=12.17' } + + cache-content-type@1.0.1: + resolution: + { + integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==, + } + engines: { node: '>= 6.0.0' } + + call-bind@1.0.5: + resolution: + { + integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==, + } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: '>=6' } + + camelcase-keys@6.2.2: + resolution: + { + integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==, + } + engines: { node: '>=8' } + + camelcase@5.3.1: + resolution: + { + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, + } + engines: { node: '>=6' } + + camelcase@6.3.0: + resolution: + { + integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==, + } + engines: { node: '>=10' } + + camelcase@7.0.1: + resolution: + { + integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==, + } + engines: { node: '>=14.16' } + + caniuse-lite@1.0.30001580: + resolution: + { + integrity: sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==, + } + + ccount@2.0.1: + resolution: + { + integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, + } + + 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' } + + chalk@5.3.0: + resolution: + { + integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==, + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + character-entities-html4@2.1.0: + resolution: + { + integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==, + } + + character-entities-legacy@3.0.0: + resolution: + { + integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==, + } + + character-entities@2.0.2: + resolution: + { + integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, + } + + character-reference-invalid@2.0.1: + resolution: + { + integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==, + } + + chardet@0.7.0: + resolution: + { + integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==, + } + + chokidar@3.5.3: + resolution: + { + integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==, + } + engines: { node: '>= 8.10.0' } + + classnames@2.5.1: + resolution: + { + integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==, + } + + cli-boxes@3.0.0: + resolution: + { + integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==, + } + engines: { node: '>=10' } + + cli-cursor@3.1.0: + resolution: + { + integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==, + } + engines: { node: '>=8' } + + cli-spinners@2.9.2: + resolution: + { + integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==, + } + engines: { node: '>=6' } + + cli-width@3.0.0: + resolution: + { + integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==, + } + engines: { node: '>= 10' } + + cliui@6.0.0: + resolution: + { + integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==, + } + + cliui@7.0.4: + resolution: + { + integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==, + } + + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: '>=12' } + + clone@1.0.4: + resolution: + { + integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==, + } + engines: { node: '>=0.8' } + + clsx@2.1.0: + resolution: + { + integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==, + } + engines: { node: '>=6' } + + co@4.6.0: + resolution: + { + integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==, + } + engines: { iojs: '>= 1.0.0', node: '>= 0.12.0' } + + collapse-white-space@2.1.0: + resolution: + { + integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==, + } + + 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==, + } + + colord@2.9.3: + resolution: + { + integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==, + } + + combined-stream@1.0.8: + resolution: + { + integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, + } + engines: { node: '>= 0.8' } + + comma-separated-tokens@2.0.3: + resolution: + { + integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==, + } + + commander@11.1.0: + resolution: + { + integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==, + } + engines: { node: '>=16' } + + commander@2.20.3: + resolution: + { + integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, + } + + commander@7.2.0: + resolution: + { + integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==, + } + engines: { node: '>= 10' } + + commander@9.5.0: + resolution: + { + integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==, + } + engines: { node: ^12.20.0 || >=14 } + + compare-func@2.0.0: + resolution: + { + integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==, + } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } + + concat-stream@2.0.0: + resolution: + { + integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==, + } + engines: { '0': node >= 6.0 } + + concurrently@8.2.2: + resolution: + { + integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==, + } + engines: { node: ^14.13.0 || >=16.0.0 } + hasBin: true + + content-disposition@0.5.4: + resolution: + { + integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==, + } + engines: { node: '>= 0.6' } + + content-type@1.0.5: + resolution: + { + integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==, + } + engines: { node: '>= 0.6' } + + conventional-changelog-angular@5.0.13: + resolution: + { + integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==, + } + engines: { node: '>=10' } + + conventional-changelog-atom@2.0.8: + resolution: + { + integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==, + } + engines: { node: '>=10' } + + conventional-changelog-codemirror@2.0.8: + resolution: + { + integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==, + } + engines: { node: '>=10' } + + conventional-changelog-config-spec@2.1.0: + resolution: + { + integrity: sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==, + } + + conventional-changelog-conventionalcommits@4.6.3: + resolution: + { + integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==, + } + engines: { node: '>=10' } + + conventional-changelog-core@4.2.4: + resolution: + { + integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==, + } + engines: { node: '>=10' } + + conventional-changelog-ember@2.0.9: + resolution: + { + integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==, + } + engines: { node: '>=10' } + + conventional-changelog-eslint@3.0.9: + resolution: + { + integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==, + } + engines: { node: '>=10' } + + conventional-changelog-express@2.0.6: + resolution: + { + integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==, + } + engines: { node: '>=10' } + + conventional-changelog-jquery@3.0.11: + resolution: + { + integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==, + } + engines: { node: '>=10' } + + conventional-changelog-jshint@2.0.9: + resolution: + { + integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==, + } + engines: { node: '>=10' } + + conventional-changelog-preset-loader@2.3.4: + resolution: + { + integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==, + } + engines: { node: '>=10' } + + conventional-changelog-writer@5.0.1: + resolution: + { + integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==, + } + engines: { node: '>=10' } + hasBin: true + + conventional-changelog@3.1.25: + resolution: + { + integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==, + } + engines: { node: '>=10' } + + conventional-commits-filter@2.0.7: + resolution: + { + integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==, + } + engines: { node: '>=10' } + + conventional-commits-parser@3.2.4: + resolution: + { + integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==, + } + engines: { node: '>=10' } + hasBin: true + + conventional-recommended-bump@6.1.0: + resolution: + { + integrity: sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==, + } + engines: { node: '>=10' } + hasBin: true + + convert-source-map@1.9.0: + resolution: + { + integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==, + } + + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, + } + + cookie@0.5.0: + resolution: + { + integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==, + } + engines: { node: '>= 0.6' } + + cookies@0.9.1: + resolution: + { + integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==, + } + engines: { node: '>= 0.8' } + + copy-anything@3.0.5: + resolution: + { + integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==, + } + engines: { node: '>=12.13' } + + core-js-compat@3.35.1: + resolution: + { + integrity: sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==, + } + + core-js@3.35.1: + resolution: + { + integrity: sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==, + } + + core-util-is@1.0.3: + resolution: + { + integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==, + } + + cosmiconfig@7.1.0: + resolution: + { + integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==, + } + engines: { node: '>=10' } + + cosmiconfig@8.3.6: + resolution: + { + integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==, + } + engines: { node: '>=14' } + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cosmiconfig@9.0.0: + resolution: + { + integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==, + } + engines: { node: '>=14' } + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.3: + resolution: + { + integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, + } + engines: { node: '>= 8' } + + css-functions-list@3.2.1: + resolution: + { + integrity: sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==, + } + engines: { node: '>=12 || >=16' } + + css-select@5.1.0: + resolution: + { + integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==, + } + + css-selector-parser@3.0.4: + resolution: + { + integrity: sha512-pnmS1dbKsz6KA4EW4BznyPL2xxkNDRg62hcD0v8g6DEw2W7hxOln5M953jsp9hmw5Dg57S6o/A8GOn37mbAgcQ==, + } + + css-tree@2.2.1: + resolution: + { + integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0' } + + css-tree@2.3.1: + resolution: + { + integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0 } + + css-what@6.1.0: + resolution: + { + integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==, + } + engines: { node: '>= 6' } + + cssesc@3.0.0: + resolution: + { + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, + } + engines: { node: '>=4' } + hasBin: true + + csso@5.0.5: + resolution: + { + integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==, + } + engines: { node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0' } + + csstype@3.1.3: + resolution: + { + integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==, + } + + d3-array@3.2.4: + resolution: + { + integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==, + } + engines: { node: '>=12' } + + d3-color@3.1.0: + resolution: + { + integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==, + } + engines: { node: '>=12' } + + d3-ease@3.0.1: + resolution: + { + integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==, + } + engines: { node: '>=12' } + + d3-format@3.1.0: + resolution: + { + integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==, + } + engines: { node: '>=12' } + + d3-interpolate@3.0.1: + resolution: + { + integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==, + } + engines: { node: '>=12' } + + d3-path@3.1.0: + resolution: + { + integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==, + } + engines: { node: '>=12' } + + d3-scale@4.0.2: + resolution: + { + integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==, + } + engines: { node: '>=12' } + + d3-shape@3.2.0: + resolution: + { + integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==, + } + engines: { node: '>=12' } + + d3-time-format@4.1.0: + resolution: + { + integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==, + } + engines: { node: '>=12' } + + d3-time@3.1.0: + resolution: + { + integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==, + } + engines: { node: '>=12' } + + d3-timer@3.0.1: + resolution: + { + integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==, + } + engines: { node: '>=12' } + + damerau-levenshtein@1.0.8: + resolution: + { + integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==, + } + + dargs@7.0.0: + resolution: + { + integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==, + } + engines: { node: '>=8' } + + dashify@2.0.0: + resolution: + { + integrity: sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A==, + } + engines: { node: '>=4' } + + date-fns@2.30.0: + resolution: + { + integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==, + } + engines: { node: '>=0.11' } + + dateformat@3.0.3: + resolution: + { + integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==, + } + + dayjs@1.11.10: + resolution: + { + integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==, + } + + debug@3.2.7: + resolution: + { + integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==, + } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.4: + resolution: + { + integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==, + } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize-keys@1.1.1: + resolution: + { + integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==, + } + engines: { node: '>=0.10.0' } + + decamelize@1.2.0: + resolution: + { + integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==, + } + engines: { node: '>=0.10.0' } + + decimal.js-light@2.5.1: + resolution: + { + integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==, + } + + decode-named-character-reference@1.0.2: + resolution: + { + integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==, + } + + decode-uri-component@0.4.1: + resolution: + { + integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==, + } + engines: { node: '>=14.16' } + + deep-equal@1.0.1: + resolution: + { + integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==, + } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } + + deepmerge@4.3.1: + resolution: + { + integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==, + } + engines: { node: '>=0.10.0' } + + default-browser-id@3.0.0: + resolution: + { + integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==, + } + engines: { node: '>=12' } + + default-browser@4.0.0: + resolution: + { + integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==, + } + engines: { node: '>=14.16' } + + defaults@1.0.4: + resolution: + { + integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==, + } + + define-data-property@1.1.1: + resolution: + { + integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==, + } + engines: { node: '>= 0.4' } + + define-lazy-prop@3.0.0: + resolution: + { + integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==, + } + engines: { node: '>=12' } + + 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==, + } + + depd@1.1.2: + resolution: + { + integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==, + } + engines: { node: '>= 0.6' } + + depd@2.0.0: + resolution: + { + integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==, + } + engines: { node: '>= 0.8' } + + dequal@2.0.3: + resolution: + { + integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, + } + engines: { node: '>=6' } + + destroy@1.2.0: + resolution: + { + integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==, + } + engines: { node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16 } + + detect-browser@5.3.0: + resolution: + { + integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==, + } + + detect-indent@6.1.0: + resolution: + { + integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==, + } + engines: { node: '>=8' } + + detect-newline@3.1.0: + resolution: + { + integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==, + } + engines: { node: '>=8' } + + devlop@1.1.0: + resolution: + { + integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, + } + + dijkstrajs@1.0.3: + resolution: + { + integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==, + } + + dir-glob@3.0.1: + resolution: + { + integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, + } + engines: { node: '>=8' } + + direction@2.0.1: + resolution: + { + integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==, + } + hasBin: true + + 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' } + + dom-helpers@3.4.0: + resolution: + { + integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==, + } + + dom-serializer@2.0.0: + resolution: + { + integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==, + } + + domelementtype@2.3.0: + resolution: + { + integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==, + } + + domhandler@5.0.3: + resolution: + { + integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==, + } + engines: { node: '>= 4' } + + domutils@3.1.0: + resolution: + { + integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==, + } + + dot-case@3.0.4: + resolution: + { + integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==, + } + + dot-prop@5.3.0: + resolution: + { + integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==, + } + engines: { node: '>=8' } + + dotgitignore@2.1.0: + resolution: + { + integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==, + } + engines: { node: '>=6' } + + eastasianwidth@0.2.0: + resolution: + { + integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, + } + + ee-first@1.1.1: + resolution: + { + integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, + } + + electron-to-chromium@1.4.645: + resolution: + { + integrity: sha512-EeS1oQDCmnYsRDRy2zTeC336a/4LZ6WKqvSaM1jLocEk5ZuyszkQtCpsqvuvaIXGOUjwtvF6LTcS8WueibXvSw==, + } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + emoji-regex@9.2.2: + resolution: + { + integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, + } + + encode-utf8@1.0.3: + resolution: + { + integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==, + } + + encodeurl@1.0.2: + resolution: + { + integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==, + } + engines: { node: '>= 0.8' } + + entities@4.5.0: + resolution: + { + integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==, + } + engines: { node: '>=0.12' } + + env-paths@2.2.1: + resolution: + { + integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==, + } + engines: { node: '>=6' } + + error-ex@1.3.2: + resolution: + { + integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, + } + + es-abstract@1.22.3: + resolution: + { + integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==, + } + engines: { node: '>= 0.4' } + + es-iterator-helpers@1.0.15: + resolution: + { + integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==, + } + + es-set-tostringtag@2.0.2: + resolution: + { + integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==, + } + 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' } + + esbuild-plugin-browserslist@0.10.0: + resolution: + { + integrity: sha512-rZWFcp3l+73xDiJB+Vl9UqP1VVs+L4E0lygbwJl6UTmW2qQago7DLT56hBu0vocH/TtZsAcRHj0+qHqkkB5Gww==, + } + engines: { node: '>=18' } + peerDependencies: + browserslist: ^4.21.8 + esbuild: ~0.19.2 + + esbuild@0.19.12: + resolution: + { + integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==, + } + engines: { node: '>=12' } + hasBin: true + + escalade@3.1.1: + resolution: + { + integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==, + } + engines: { node: '>=6' } + + escape-html@1.0.3: + resolution: + { + integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, + } + + 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' } + + escape-string-regexp@5.0.0: + resolution: + { + integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, + } + engines: { node: '>=12' } + + eslint-config-prettier@9.1.0: + resolution: + { + integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==, + } + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-import-resolver-node@0.3.9: + resolution: + { + integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==, + } + + 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-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-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-prettier@5.1.3: + resolution: + { + integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-react-hooks@4.6.0: + resolution: + { + integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==, + } + 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-refresh@0.4.5: + resolution: + { + integrity: sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==, + } + peerDependencies: + eslint: '>=7' + + eslint-plugin-react@7.33.2: + resolution: + { + integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==, + } + engines: { node: '>=4' } + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-simple-import-sort@10.0.0: + resolution: + { + integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==, + } + peerDependencies: + eslint: '>=5.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@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.56.0: + resolution: + { + integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==, + } + 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@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: '>=4.0' } + + estree-util-attach-comments@3.0.0: + resolution: + { + integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==, + } + + estree-util-build-jsx@3.0.1: + resolution: + { + integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==, + } + + estree-util-is-identifier-name@3.0.0: + resolution: + { + integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==, + } + + estree-util-to-js@2.0.0: + resolution: + { + integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==, + } + + estree-util-visit@2.0.0: + resolution: + { + integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==, + } + + estree-walker@2.0.2: + resolution: + { + integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, + } + + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: '>=0.10.0' } + + ethers@6.10.0: + resolution: + { + integrity: sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==, + } + engines: { node: '>=14.0.0' } + + eventemitter3@4.0.7: + resolution: + { + integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==, + } + + events@3.3.0: + resolution: + { + integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==, + } + engines: { node: '>=0.8.x' } + + execa@5.1.1: + resolution: + { + integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, + } + engines: { node: '>=10' } + + execa@7.2.0: + resolution: + { + integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==, + } + engines: { node: ^14.18.0 || ^16.14.0 || >=18.0.0 } + + extend@3.0.2: + resolution: + { + integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, + } + + external-editor@3.1.0: + resolution: + { + integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==, + } + engines: { node: '>=4' } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-diff@1.3.0: + resolution: + { + integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, + } + + fast-equals@5.0.1: + resolution: + { + integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==, + } + engines: { node: '>=6.0.0' } + + 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==, + } + + fastest-levenshtein@1.0.16: + resolution: + { + integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==, + } + engines: { node: '>= 4.9.1' } + + fastq@1.16.0: + resolution: + { + integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==, + } + + figures@3.2.0: + resolution: + { + integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==, + } + engines: { node: '>=8' } + + file-entry-cache@6.0.1: + resolution: + { + integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==, + } + engines: { node: ^10.12.0 || >=12.0.0 } + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: '>=16.0.0' } + + file-saver@2.0.5: + resolution: + { + integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==, + } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: '>=8' } + + filter-obj@5.1.0: + resolution: + { + integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==, + } + engines: { node: '>=14.16' } + + find-root@1.1.0: + resolution: + { + integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==, + } + + find-up@2.1.0: + resolution: + { + integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==, + } + engines: { node: '>=4' } + + find-up@3.0.0: + resolution: + { + integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==, + } + engines: { node: '>=6' } + + 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 } + + flat-cache@4.0.0: + resolution: + { + integrity: sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA==, + } + engines: { node: '>=16' } + + flatted@3.2.9: + resolution: + { + integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==, + } + + follow-redirects@1.15.5: + resolution: + { + integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==, + } + 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@4.0.0: + resolution: + { + integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==, + } + engines: { node: '>= 6' } + + fraction.js@4.3.7: + resolution: + { + integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==, + } + + framer-motion@11.0.3: + resolution: + { + integrity: sha512-6x2poQpIWBdbZwLd73w6cKZ1I9IEPIU94C6/Swp1Zt3LJ+sB5bPe1E2wC6EH5hSISXNkMJ4afH7AdwS7MrtkWw==, + } + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + + fresh@0.5.2: + resolution: + { + integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==, + } + engines: { node: '>= 0.6' } + + fs.realpath@1.0.0: + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, + } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + 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==, + } + + 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.2: + resolution: + { + integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==, + } + + get-pkg-repo@4.2.1: + resolution: + { + integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==, + } + engines: { node: '>=6.9.0' } + hasBin: true + + get-port@7.0.0: + resolution: + { + integrity: sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==, + } + engines: { node: '>=16' } + + get-stream@6.0.1: + resolution: + { + integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, + } + engines: { node: '>=10' } + + get-symbol-description@1.0.0: + resolution: + { + integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==, + } + engines: { node: '>= 0.4' } + + get-text-width@1.0.3: + resolution: + { + integrity: sha512-kv1MaexPcR/qaZ4kN8sUDjG5pRp5ptHvxcDGDBTeGld1cmo7MnlCMH22jevyvs/VV7Ran203o7qAOq2+kWw9cA==, + } + + git-raw-commits@2.0.11: + resolution: + { + integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==, + } + engines: { node: '>=10' } + hasBin: true + + git-remote-origin-url@2.0.0: + resolution: + { + integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==, + } + engines: { node: '>=4' } + + git-semver-tags@4.1.1: + resolution: + { + integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==, + } + engines: { node: '>=10' } + hasBin: true + + gitconfiglocal@1.0.0: + resolution: + { + integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==, + } + + 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 + + glob@8.1.0: + resolution: + { + integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==, + } + engines: { node: '>=12' } + deprecated: Glob versions prior to v9 are no longer supported + + global-modules@2.0.0: + resolution: + { + integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==, + } + engines: { node: '>=6' } + + global-prefix@3.0.0: + resolution: + { + integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==, + } + engines: { node: '>=6' } + + 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@14.0.0: + resolution: + { + integrity: sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==, + } + engines: { node: '>=18' } + + globjoin@0.1.4: + resolution: + { + integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==, + } + + globrex@0.1.2: + resolution: + { + integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==, + } + + 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==, + } + + graphql@16.8.1: + resolution: + { + integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==, + } + engines: { node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0 } + + handlebars@4.7.8: + resolution: + { + integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==, + } + engines: { node: '>=0.4.7' } + hasBin: true + + hard-rejection@2.1.0: + resolution: + { + integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==, + } + engines: { node: '>=6' } + + 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.1: + resolution: + { + integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==, + } + + has-proto@1.0.1: + resolution: + { + integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==, + } + engines: { node: '>= 0.4' } + + has-symbols@1.0.3: + resolution: + { + integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==, + } + engines: { node: '>= 0.4' } + + has-tostringtag@1.0.0: + resolution: + { + integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==, + } + engines: { node: '>= 0.4' } + + hasown@2.0.0: + resolution: + { + integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==, + } + engines: { node: '>= 0.4' } + + hast-util-classnames@3.0.0: + resolution: + { + integrity: sha512-tI3JjoGDEBVorMAWK4jNRsfLMYmih1BUOG3VV36pH36njs1IEl7xkNrVTD2mD2yYHmQCa5R/fj61a8IAF4bRaQ==, + } + + hast-util-from-parse5@8.0.1: + resolution: + { + integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==, + } + + hast-util-has-property@3.0.0: + resolution: + { + integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==, + } + + hast-util-parse-selector@4.0.0: + resolution: + { + integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==, + } + + hast-util-raw@9.0.2: + resolution: + { + integrity: sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==, + } + + hast-util-select@6.0.2: + resolution: + { + integrity: sha512-hT/SD/d/Meu+iobvgkffo1QecV8WeKWxwsNMzcTJsKw1cKTQKSR/7ArJeURLNJF9HDjp9nVoORyNNJxrvBye8Q==, + } + + hast-util-to-estree@3.1.0: + resolution: + { + integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==, + } + + hast-util-to-jsx-runtime@2.3.0: + resolution: + { + integrity: sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==, + } + + hast-util-to-parse5@8.0.0: + resolution: + { + integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==, + } + + hast-util-to-string@3.0.0: + resolution: + { + integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==, + } + + hast-util-whitespace@3.0.0: + resolution: + { + integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==, + } + + hastscript@8.0.0: + resolution: + { + integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==, + } + + headers-polyfill@4.0.2: + resolution: + { + integrity: sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==, + } + + hex-rgb@5.0.0: + resolution: + { + integrity: sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w==, + } + engines: { node: '>=12' } + + history@5.3.0: + resolution: + { + integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==, + } + + hoist-non-react-statics@3.3.2: + resolution: + { + integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==, + } + + hosted-git-info@2.8.9: + resolution: + { + integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==, + } + + hosted-git-info@4.1.0: + resolution: + { + integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==, + } + engines: { node: '>=10' } + + html-dom-parser@5.0.7: + resolution: + { + integrity: sha512-2YD2/yB0QgrlkBIn0CsGaRXC89E1gtuPVpiOGC52NTzPCC83n0WMdGD+5q7lpcKqbCpnWValQbovuy/NI/0kag==, + } + + html-react-parser@5.1.1: + resolution: + { + integrity: sha512-L5VK0rKN3VM7uzRH+4wxAL9elvHuCNDjyWKKjcCDR+YWW5Qr7WWSK7+e627DcePVAFi5IMqc+rAU8j/1DpC/Tw==, + } + peerDependencies: + react: 0.14 || 15 || 16 || 17 || 18 + + html-tags@3.3.1: + resolution: + { + integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==, + } + engines: { node: '>=8' } + + html-url-attributes@3.0.0: + resolution: + { + integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==, + } + + html-void-elements@3.0.0: + resolution: + { + integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==, + } + + htmlparser2@9.1.0: + resolution: + { + integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==, + } + + http-assert@1.5.0: + resolution: + { + integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==, + } + engines: { node: '>= 0.8' } + + http-errors@1.8.1: + resolution: + { + integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==, + } + engines: { node: '>= 0.6' } + + human-signals@2.1.0: + resolution: + { + integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, + } + engines: { node: '>=10.17.0' } + + human-signals@4.3.1: + resolution: + { + integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==, + } + engines: { node: '>=14.18.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.0: + resolution: + { + integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==, + } + engines: { node: '>= 4' } + + immutable@4.3.4: + resolution: + { + integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==, + } + + 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.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, + } + + ini@1.3.8: + resolution: + { + integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==, + } + + inline-style-parser@0.1.1: + resolution: + { + integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==, + } + + inline-style-parser@0.2.2: + resolution: + { + integrity: sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==, + } + + inquirer@8.2.6: + resolution: + { + integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==, + } + engines: { node: '>=12.0.0' } + + internal-slot@1.0.6: + resolution: + { + integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==, + } + engines: { node: '>= 0.4' } + + internmap@2.0.3: + resolution: + { + integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==, + } + engines: { node: '>=12' } + + is-alphabetical@2.0.1: + resolution: + { + integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==, + } + + is-alphanumerical@2.0.1: + resolution: + { + integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==, + } + + is-array-buffer@3.0.2: + resolution: + { + integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==, + } + + is-arrayish@0.2.1: + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, + } + + 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-buffer@2.0.5: + resolution: + { + integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==, + } + engines: { node: '>=4' } + + 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-date-object@1.0.5: + resolution: + { + integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==, + } + engines: { node: '>= 0.4' } + + is-decimal@2.0.1: + resolution: + { + integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==, + } + + is-docker@2.2.1: + resolution: + { + integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==, + } + engines: { node: '>=8' } + hasBin: true + + is-docker@3.0.0: + resolution: + { + integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + hasBin: true + + 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-hexadecimal@2.0.1: + resolution: + { + integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==, + } + + is-inside-container@1.0.0: + resolution: + { + integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, + } + engines: { node: '>=14.16' } + hasBin: true + + is-interactive@1.0.0: + resolution: + { + integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==, + } + engines: { node: '>=8' } + + is-map@2.0.2: + resolution: + { + integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==, + } + + is-negative-zero@2.0.2: + resolution: + { + integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==, + } + engines: { node: '>= 0.4' } + + is-node-process@1.2.0: + resolution: + { + integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==, + } + + 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@1.1.0: + resolution: + { + integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==, + } + engines: { node: '>=0.10.0' } + + is-plain-obj@4.1.0: + resolution: + { + integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, + } + engines: { node: '>=12' } + + is-plain-object@5.0.0: + resolution: + { + integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==, + } + engines: { node: '>=0.10.0' } + + is-reference@3.0.2: + resolution: + { + integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==, + } + + is-regex@1.1.4: + resolution: + { + integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==, + } + engines: { node: '>= 0.4' } + + is-set@2.0.2: + resolution: + { + integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==, + } + + is-shared-array-buffer@1.0.2: + resolution: + { + integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==, + } + + is-stream@2.0.1: + resolution: + { + integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, + } + engines: { node: '>=8' } + + is-stream@3.0.0: + resolution: + { + integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + 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-text-path@1.0.1: + resolution: + { + integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==, + } + engines: { node: '>=0.10.0' } + + is-typed-array@1.1.12: + resolution: + { + integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==, + } + engines: { node: '>= 0.4' } + + is-unicode-supported@0.1.0: + resolution: + { + integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==, + } + engines: { node: '>=10' } + + is-weakmap@2.0.1: + resolution: + { + integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==, + } + + is-weakref@1.0.2: + resolution: + { + integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==, + } + + is-weakset@2.0.2: + resolution: + { + integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==, + } + + is-what@4.1.16: + resolution: + { + integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==, + } + engines: { node: '>=12.13' } + + is-wsl@2.2.0: + resolution: + { + integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, + } + engines: { node: '>=8' } + + isarray@1.0.0: + resolution: + { + integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==, + } + + isarray@2.0.5: + resolution: + { + integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, + } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + iterator.prototype@1.1.2: + resolution: + { + integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==, + } + + itertools@2.2.3: + resolution: + { + integrity: sha512-TV4TDJ2FrLxhRJDX/AgdyI76i6cHi2Z1hml/d+HLcGVHxmgfxsLpoQBN2ZE9OizPt10+VW+LamLfCDASlnxvNg==, + } + + jackspeak@2.3.6: + resolution: + { + integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==, + } + engines: { node: '>=14' } + + 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 + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + + json-parse-better-errors@1.0.2: + resolution: + { + integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==, + } + + json-parse-even-better-errors@2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, + } + + 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==, + } + + 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 + + jsonc-parser@3.2.1: + resolution: + { + integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==, + } + + jsonparse@1.3.1: + resolution: + { + integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==, + } + engines: { '0': node >= 0.2.0 } + + jsx-ast-utils@3.3.5: + resolution: + { + integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==, + } + engines: { node: '>=4.0' } + + keygrip@1.1.0: + resolution: + { + integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==, + } + engines: { node: '>= 0.6' } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } + + kind-of@6.0.3: + resolution: + { + integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, + } + engines: { node: '>=0.10.0' } + + known-css-properties@0.29.0: + resolution: + { + integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==, + } + + koa-compose@4.1.0: + resolution: + { + integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==, + } + + koa-connect@2.1.0: + resolution: + { + integrity: sha512-O9pcFafHk0oQsBevlbTBlB9co+2RUQJ4zCzu3qJPmGlGoeEZkne+7gWDkecqDPSbCtED6LmhlQladxs6NjOnMQ==, + } + + koa-convert@2.0.0: + resolution: + { + integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==, + } + engines: { node: '>= 10' } + + koa@2.15.0: + resolution: + { + integrity: sha512-KEL/vU1knsoUvfP4MC4/GthpQrY/p6dzwaaGI6Rt4NQuFqkw3qrvsdYF5pz3wOfi7IGTvMPHC9aZIcUKYFNxsw==, + } + engines: { node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4 } + + 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' } + + lines-and-columns@1.2.4: + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, + } + + little-state-machine@4.8.0: + resolution: + { + integrity: sha512-xfi5+iDxTLhu0hbnNubUs+qoQQqxhtEZeObP5ELjUlHnl74bbasY7mOonsGQrAouyrbag3ebNLSse5xX1T7buQ==, + } + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + + load-json-file@4.0.0: + resolution: + { + integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==, + } + engines: { node: '>=4' } + + locate-path@2.0.0: + resolution: + { + integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==, + } + engines: { node: '>=4' } + + locate-path@3.0.0: + resolution: + { + integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==, + } + engines: { node: '>=6' } + + 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-es@4.17.21: + resolution: + { + integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==, + } + + lodash.debounce@4.0.8: + resolution: + { + integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, + } + + lodash.ismatch@4.4.0: + resolution: + { + integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==, + } + + lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, + } + + lodash.truncate@4.4.2: + resolution: + { + integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==, + } + + lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, + } + + log-symbols@4.1.0: + resolution: + { + integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, + } + engines: { node: '>=10' } + + longest-streak@3.1.0: + resolution: + { + integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, + } + + loose-envify@1.4.0: + resolution: + { + integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, + } + hasBin: true + + lower-case@2.0.2: + resolution: + { + integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==, + } + + lru-cache@10.1.0: + resolution: + { + integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==, + } + 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' } + + lunr@2.3.9: + resolution: + { + integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==, + } + + magic-string@0.30.5: + resolution: + { + integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==, + } + engines: { node: '>=12' } + + map-obj@1.0.1: + resolution: + { + integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==, + } + engines: { node: '>=0.10.0' } + + map-obj@4.3.0: + resolution: + { + integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==, + } + engines: { node: '>=8' } + + markdown-extensions@2.0.0: + resolution: + { + integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==, + } + engines: { node: '>=16' } + + markdown-table@3.0.3: + resolution: + { + integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==, + } + + marked@4.3.0: + resolution: + { + integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==, + } + engines: { node: '>= 12' } + hasBin: true + + mathml-tag-names@2.1.3: + resolution: + { + integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==, + } + + mdast-util-find-and-replace@3.0.1: + resolution: + { + integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==, + } + + mdast-util-from-markdown@2.0.0: + resolution: + { + integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==, + } + + mdast-util-gfm-autolink-literal@2.0.0: + resolution: + { + integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==, + } + + mdast-util-gfm-footnote@2.0.0: + resolution: + { + integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==, + } + + mdast-util-gfm-strikethrough@2.0.0: + resolution: + { + integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==, + } + + mdast-util-gfm-table@2.0.0: + resolution: + { + integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==, + } + + mdast-util-gfm-task-list-item@2.0.0: + resolution: + { + integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==, + } + + mdast-util-gfm@3.0.0: + resolution: + { + integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==, + } + + mdast-util-mdx-expression@2.0.0: + resolution: + { + integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==, + } + + mdast-util-mdx-jsx@3.0.0: + resolution: + { + integrity: sha512-XZuPPzQNBPAlaqsTTgRrcJnyFbSOBovSadFgbFu8SnuNgm+6Bdx1K+IWoitsmj6Lq6MNtI+ytOqwN70n//NaBA==, + } + + mdast-util-mdx@3.0.0: + resolution: + { + integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==, + } + + mdast-util-mdxjs-esm@2.0.1: + resolution: + { + integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==, + } + + mdast-util-phrasing@4.0.0: + resolution: + { + integrity: sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==, + } + + mdast-util-to-hast@13.1.0: + resolution: + { + integrity: sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==, + } + + mdast-util-to-markdown@2.1.0: + resolution: + { + integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==, + } + + mdast-util-to-string@4.0.0: + resolution: + { + integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, + } + + mdn-data@2.0.28: + resolution: + { + integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==, + } + + mdn-data@2.0.30: + resolution: + { + integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==, + } + + media-typer@0.3.0: + resolution: + { + integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==, + } + engines: { node: '>= 0.6' } + + memoize-one@5.2.1: + resolution: + { + integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==, + } + + meow@13.1.0: + resolution: + { + integrity: sha512-o5R/R3Tzxq0PJ3v3qcQJtSvSE9nKOLSAaDuuoMzDVuGTwHdccMWcYomh9Xolng2tjT6O/Y83d+0coVGof6tqmA==, + } + engines: { node: '>=18' } + + meow@8.1.2: + resolution: + { + integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==, + } + 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' } + + micromark-core-commonmark@2.0.0: + resolution: + { + integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==, + } + + micromark-extension-gfm-autolink-literal@2.0.0: + resolution: + { + integrity: sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==, + } + + micromark-extension-gfm-footnote@2.0.0: + resolution: + { + integrity: sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==, + } + + micromark-extension-gfm-strikethrough@2.0.0: + resolution: + { + integrity: sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==, + } + + micromark-extension-gfm-table@2.0.0: + resolution: + { + integrity: sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==, + } + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: + { + integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==, + } + + micromark-extension-gfm-task-list-item@2.0.1: + resolution: + { + integrity: sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==, + } + + micromark-extension-gfm@3.0.0: + resolution: + { + integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==, + } + + micromark-extension-mdx-expression@3.0.0: + resolution: + { + integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==, + } + + micromark-extension-mdx-jsx@3.0.0: + resolution: + { + integrity: sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==, + } + + micromark-extension-mdx-md@2.0.0: + resolution: + { + integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==, + } + + micromark-extension-mdxjs-esm@3.0.0: + resolution: + { + integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==, + } + + micromark-extension-mdxjs@3.0.0: + resolution: + { + integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==, + } + + micromark-factory-destination@2.0.0: + resolution: + { + integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==, + } + + micromark-factory-label@2.0.0: + resolution: + { + integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==, + } + + micromark-factory-mdx-expression@2.0.1: + resolution: + { + integrity: sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==, + } + + micromark-factory-space@2.0.0: + resolution: + { + integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==, + } + + micromark-factory-title@2.0.0: + resolution: + { + integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==, + } + + micromark-factory-whitespace@2.0.0: + resolution: + { + integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==, + } + + micromark-util-character@2.0.1: + resolution: + { + integrity: sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==, + } + + micromark-util-chunked@2.0.0: + resolution: + { + integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==, + } + + micromark-util-classify-character@2.0.0: + resolution: + { + integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==, + } + + micromark-util-combine-extensions@2.0.0: + resolution: + { + integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==, + } + + micromark-util-decode-numeric-character-reference@2.0.1: + resolution: + { + integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==, + } + + micromark-util-decode-string@2.0.0: + resolution: + { + integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==, + } + + micromark-util-encode@2.0.0: + resolution: + { + integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==, + } + + micromark-util-events-to-acorn@2.0.2: + resolution: + { + integrity: sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==, + } + + micromark-util-html-tag-name@2.0.0: + resolution: + { + integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==, + } + + micromark-util-normalize-identifier@2.0.0: + resolution: + { + integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==, + } + + micromark-util-resolve-all@2.0.0: + resolution: + { + integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==, + } + + micromark-util-sanitize-uri@2.0.0: + resolution: + { + integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==, + } + + micromark-util-subtokenize@2.0.0: + resolution: + { + integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==, + } + + micromark-util-symbol@2.0.0: + resolution: + { + integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==, + } + + micromark-util-types@2.0.0: + resolution: + { + integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==, + } + + micromark@4.0.0: + resolution: + { + integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==, + } + + micromatch@4.0.5: + resolution: + { + integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==, + } + 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' } + + mimic-fn@2.1.0: + resolution: + { + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, + } + engines: { node: '>=6' } + + mimic-fn@4.0.0: + resolution: + { + integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==, + } + engines: { node: '>=12' } + + 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@5.1.6: + resolution: + { + integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==, + } + engines: { node: '>=10' } + + minimatch@9.0.3: + resolution: + { + integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==, + } + engines: { node: '>=16 || 14 >=14.17' } + + minimist-options@4.1.0: + resolution: + { + integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==, + } + engines: { node: '>= 6' } + + minimist@1.2.8: + resolution: + { + integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, + } + + minipass@7.0.4: + resolution: + { + integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==, + } + engines: { node: '>=16 || 14 >=14.17' } + + modify-values@1.0.1: + resolution: + { + integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==, + } + engines: { node: '>=0.10.0' } + + ms@2.1.2: + resolution: + { + integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, + } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + msw@2.1.4: + resolution: + { + integrity: sha512-YyIQpfLqAJf/O1kYPWBSbDqjgv71kRBmEbGLxkkai1Btcs/LcxKiAwT1My3COa9J/vTh9Ua41B/ReuiW9cXmkw==, + } + engines: { node: '>=18' } + hasBin: true + peerDependencies: + typescript: '>= 4.7.x <= 5.3.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@0.0.8: + resolution: + { + integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==, + } + + nanoid@3.3.7: + resolution: + { + integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } + + negotiator@0.6.3: + resolution: + { + integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==, + } + engines: { node: '>= 0.6' } + + neo-async@2.6.2: + resolution: + { + integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==, + } + + no-case@3.0.4: + resolution: + { + integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==, + } + + node-releases@2.0.14: + resolution: + { + integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==, + } + + normalize-package-data@2.5.0: + resolution: + { + integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==, + } + + normalize-package-data@3.0.3: + resolution: + { + integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==, + } + engines: { node: '>=10' } + + normalize-path@3.0.0: + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, + } + engines: { node: '>=0.10.0' } + + normalize-range@0.1.2: + resolution: + { + integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==, + } + engines: { node: '>=0.10.0' } + + not@0.1.0: + resolution: + { + integrity: sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==, + } + + npm-run-path@4.0.1: + resolution: + { + integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, + } + engines: { node: '>=8' } + + npm-run-path@5.2.0: + resolution: + { + integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==, + } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + nth-check@2.1.1: + resolution: + { + integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, + } + + numbro@2.4.0: + resolution: + { + integrity: sha512-t6rVkO1CcKvffvOJJu/zMo70VIcQSR6w3AmIhfHGvmk4vHbNe6zHgomB0aWFAPZWM9JBVWBM0efJv9DBiRoSTA==, + } + + 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' } + + on-finished@2.4.1: + resolution: + { + integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==, + } + engines: { node: '>= 0.8' } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, + } + + onetime@5.1.2: + resolution: + { + integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, + } + engines: { node: '>=6' } + + onetime@6.0.0: + resolution: + { + integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==, + } + engines: { node: '>=12' } + + only@0.0.2: + resolution: + { + integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==, + } + + open@9.1.0: + resolution: + { + integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==, + } + engines: { node: '>=14.16' } + + optionator@0.9.3: + resolution: + { + integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==, + } + engines: { node: '>= 0.8.0' } + + ora@5.4.1: + resolution: + { + integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==, + } + engines: { node: '>=10' } + + os-tmpdir@1.0.2: + resolution: + { + integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==, + } + engines: { node: '>=0.10.0' } + + outvariant@1.4.2: + resolution: + { + integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==, + } + + p-limit@1.3.0: + resolution: + { + integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==, + } + engines: { node: '>=4' } + + 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@2.0.0: + resolution: + { + integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==, + } + engines: { node: '>=4' } + + p-locate@3.0.0: + resolution: + { + integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==, + } + engines: { node: '>=6' } + + 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@1.0.0: + resolution: + { + integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==, + } + engines: { node: '>=4' } + + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: '>=6' } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: '>=6' } + + parse-entities@4.0.1: + resolution: + { + integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==, + } + + parse-json@4.0.0: + resolution: + { + integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==, + } + engines: { node: '>=4' } + + parse-json@5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, + } + engines: { node: '>=8' } + + parse5@7.1.2: + resolution: + { + integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==, + } + + parseurl@1.3.3: + resolution: + { + integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, + } + engines: { node: '>= 0.8' } + + path-exists@3.0.0: + resolution: + { + integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==, + } + engines: { node: '>=4' } + + 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-key@4.0.0: + resolution: + { + integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, + } + engines: { node: '>=12' } + + path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, + } + + path-scurry@1.10.1: + resolution: + { + integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==, + } + engines: { node: '>=16 || 14 >=14.17' } + + path-to-regexp@6.2.1: + resolution: + { + integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==, + } + + path-type@3.0.0: + resolution: + { + integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==, + } + engines: { node: '>=4' } + + path-type@4.0.0: + resolution: + { + integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, + } + engines: { node: '>=8' } + + path-type@5.0.0: + resolution: + { + integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==, + } + engines: { node: '>=12' } + + periscopic@3.1.0: + resolution: + { + integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==, + } + + picocolors@1.0.0: + resolution: + { + integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: '>=8.6' } + + pify@2.3.0: + resolution: + { + integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==, + } + engines: { node: '>=0.10.0' } + + pify@3.0.0: + resolution: + { + integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==, + } + engines: { node: '>=4' } + + pngjs@5.0.0: + resolution: + { + integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==, + } + engines: { node: '>=10.13.0' } + + postcss-media-query-parser@0.2.3: + resolution: + { + integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==, + } + + postcss-resolve-nested-selector@0.1.1: + resolution: + { + integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==, + } + + postcss-safe-parser@7.0.0: + resolution: + { + integrity: sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==, + } + engines: { node: '>=18.0' } + peerDependencies: + postcss: ^8.4.31 + + postcss-scss@4.0.9: + resolution: + { + integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==, + } + engines: { node: '>=12.0' } + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@6.0.15: + resolution: + { + integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==, + } + engines: { node: '>=4' } + + postcss-value-parser@4.2.0: + resolution: + { + integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==, + } + + postcss@8.4.33: + resolution: + { + integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==, + } + engines: { node: ^10 || ^12 || >=14 } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: '>= 0.8.0' } + + prettier-linter-helpers@1.0.0: + resolution: + { + integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==, + } + engines: { node: '>=6.0.0' } + + prettier@2.8.8: + resolution: + { + integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==, + } + engines: { node: '>=10.13.0' } + hasBin: true + + prettier@3.2.4: + resolution: + { + integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==, + } + engines: { node: '>=14' } + hasBin: true + + prism-react-renderer@2.3.1: + resolution: + { + integrity: sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==, + } + peerDependencies: + react: '>=16.0.0' + + process-nextick-args@2.0.1: + resolution: + { + integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==, + } + + prop-types@15.8.1: + resolution: + { + integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, + } + + property-information@6.4.1: + resolution: + { + integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==, + } + + proxy-from-env@1.1.0: + resolution: + { + integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, + } + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: '>=6' } + + q@1.5.1: + resolution: + { + integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==, + } + engines: { node: '>=0.6.0', teleport: '>=0.2.0' } + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + + qr.js@0.0.0: + resolution: + { + integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==, + } + + qrcode@1.5.3: + resolution: + { + integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==, + } + engines: { node: '>=10.13.0' } + hasBin: true + + query-string@8.1.0: + resolution: + { + integrity: sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==, + } + engines: { node: '>=14.16' } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + + quick-lru@4.0.1: + resolution: + { + integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==, + } + engines: { node: '>=8' } + + radash@11.0.0: + resolution: + { + integrity: sha512-CRWxTFTDff0IELGJ/zz58yY4BDgyI14qSM5OLNKbCItJrff7m7dXbVF0kWYVCXQtPb3SXIVhXvAImH6eT7VLSg==, + } + engines: { node: '>=14.18.0' } + + react-click-away-listener@2.2.3: + resolution: + { + integrity: sha512-p63JRQtK9d085+QHUJ2Pje22P/N4tEaXsS2x7tbbptriQqZ9o8xEk7G1JrxwND5YmEVc/VO4fC3+cSBsqqgLUQ==, + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + + react-dom@18.2.0: + resolution: + { + integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==, + } + peerDependencies: + react: ^18.2.0 + + react-hook-form@7.49.3: + resolution: + { + integrity: sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==, + } + engines: { node: '>=18', pnpm: '8' } + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + + react-hotkeys-hook@4.4.4: + resolution: + { + integrity: sha512-wzZmqb/Obr0ds9Myc1sIFPJ52GA/Eeg/vXBWV0HA1LvHlVAW5Va3KB0q6EZNlNSHQWscWZ2K8+6w0GYSie2o7A==, + } + peerDependencies: + react: '>=16.8.1' + react-dom: '>=16.8.1' + + react-idle-timer@5.7.2: + resolution: + { + integrity: sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ==, + } + peerDependencies: + react: '>=16' + react-dom: '>=16' + + react-inspector@6.0.2: + resolution: + { + integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==, + } + peerDependencies: + react: ^16.8.4 || ^17.0.0 || ^18.0.0 + + react-is@16.13.1: + resolution: + { + integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, + } + + react-is@18.2.0: + resolution: + { + integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==, + } + + react-lifecycles-compat@3.0.4: + resolution: + { + integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==, + } + + react-loading-skeleton@3.3.1: + resolution: + { + integrity: sha512-NilqqwMh2v9omN7LteiDloEVpFyMIa0VGqF+ukqp0ncVlYu1sKYbYGX9JEl+GtOT9TKsh04zCHAbavnQ2USldA==, + } + peerDependencies: + react: '>=16.8.0' + + react-markdown@9.0.1: + resolution: + { + integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==, + } + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-property@2.0.2: + resolution: + { + integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==, + } + + react-qr-code@2.0.12: + resolution: + { + integrity: sha512-k+pzP5CKLEGBRwZsDPp98/CAJeXlsYRHM2iZn1Sd5Th/HnKhIZCSg27PXO58zk8z02RaEryg+60xa4vyywMJwg==, + } + peerDependencies: + react: ^16.x || ^17.x || ^18.x + react-native-svg: '*' + peerDependenciesMeta: + react-native-svg: + optional: true + + react-refresh@0.14.0: + resolution: + { + integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==, + } + engines: { node: '>=0.10.0' } + + react-router-dom@6.21.3: + resolution: + { + integrity: sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==, + } + engines: { node: '>=14.0.0' } + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.21.3: + resolution: + { + integrity: sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg==, + } + engines: { node: '>=14.0.0' } + peerDependencies: + react: '>=16.8' + + react-simple-animate@3.5.2: + resolution: + { + integrity: sha512-xLE65euP920QMTOmv5haPlml+hmOPDkbIr5WeF7ADIXWBYt5kW/vwpNfWg8EKMab8aeDxIZ6QjffVh8v2dUyhg==, + } + peerDependencies: + react-dom: ^16.8.0 || ^17 || ^18 + + react-smooth@2.0.5: + resolution: + { + integrity: sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA==, + } + peerDependencies: + prop-types: ^15.6.0 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + + react-transition-group@2.9.0: + resolution: + { + integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==, + } + peerDependencies: + react: '>=15.0.0' + react-dom: '>=15.0.0' + + react-virtualized-auto-sizer@1.0.21: + resolution: + { + integrity: sha512-RedZxj452+ITLfqIrR02BjvCaXV63YVIcVrvmruDZXFpJGazg4gHNs1AShPGVLvEuLGZdZ9AtkGKhWvzEujL8g==, + } + peerDependencies: + react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + + react-window@1.8.10: + resolution: + { + integrity: sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==, + } + engines: { node: '>8.0.0' } + peerDependencies: + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + + react@18.2.0: + resolution: + { + integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==, + } + engines: { node: '>=0.10.0' } + + read-pkg-up@3.0.0: + resolution: + { + integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==, + } + engines: { node: '>=4' } + + read-pkg-up@7.0.1: + resolution: + { + integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==, + } + engines: { node: '>=8' } + + read-pkg@3.0.0: + resolution: + { + integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==, + } + engines: { node: '>=4' } + + read-pkg@5.2.0: + resolution: + { + integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==, + } + engines: { node: '>=8' } + + readable-stream@2.3.8: + resolution: + { + integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==, + } + + readable-stream@3.6.2: + resolution: + { + integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==, + } + engines: { node: '>= 6' } + + readdirp@3.6.0: + resolution: + { + integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, + } + engines: { node: '>=8.10.0' } + + recharts-scale@0.4.5: + resolution: + { + integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==, + } + + recharts@2.10.4: + resolution: + { + integrity: sha512-/Q7/wdf8bW91lN3NEeCjL9RWfaiXQViJFgdnas4Eix/I8B9HAI3tHHK/CW/zDfgRMh4fzW1zlfjoz1IAapLO1Q==, + } + engines: { node: '>=14' } + peerDependencies: + prop-types: ^15.6.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + + redent@3.0.0: + resolution: + { + integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==, + } + engines: { node: '>=8' } + + reflect.getprototypeof@1.0.4: + resolution: + { + integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==, + } + engines: { node: '>= 0.4' } + + regenerate-unicode-properties@10.1.1: + resolution: + { + integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==, + } + engines: { node: '>=4' } + + regenerate@1.4.2: + resolution: + { + integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==, + } + + regenerator-runtime@0.14.1: + resolution: + { + integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==, + } + + regenerator-transform@0.15.2: + resolution: + { + integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==, + } + + regexp.prototype.flags@1.5.1: + resolution: + { + integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==, + } + engines: { node: '>= 0.4' } + + regexpu-core@5.3.2: + resolution: + { + integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==, + } + engines: { node: '>=4' } + + regjsparser@0.9.1: + resolution: + { + integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==, + } + hasBin: true + + rehype-class-names@1.0.14: + resolution: + { + integrity: sha512-eFBt6Qxb7K77y6P82tUtN9rKpU7guWlaK4XA4RrrSFHkUTCvr2D3cgb9OR5d4t1AaGOvR59FH9nRwUnbpn9AEg==, + } + + rehype-raw@7.0.0: + resolution: + { + integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==, + } + + remark-gfm@4.0.0: + resolution: + { + integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==, + } + + remark-mdx@3.0.0: + resolution: + { + integrity: sha512-O7yfjuC6ra3NHPbRVxfflafAj3LTwx3b73aBvkEFU5z4PsD6FD4vrqJAkE5iNGLz71GdjXfgRqm3SQ0h0VuE7g==, + } + + remark-parse@11.0.0: + resolution: + { + integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==, + } + + remark-rehype@11.1.0: + resolution: + { + integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==, + } + + remark-stringify@11.0.0: + resolution: + { + integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==, + } + + remove-accents@0.4.2: + resolution: + { + integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==, + } + + 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@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 + + restore-cursor@3.1.0: + resolution: + { + integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==, + } + engines: { node: '>=8' } + + 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 + + rimraf@5.0.5: + resolution: + { + integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==, + } + engines: { node: '>=14' } + hasBin: true + + rollup-plugin-preserve-directives@0.3.1: + resolution: + { + integrity: sha512-Jn1gWU7G55A1sU6eFpXmwknfBasF0XbBzRqsE6nqrb/gun+mGV7nx++CwOSGPJQpFzFqvKm5U4XNKo3LTLi4Hg==, + } + peerDependencies: + rollup: 2.x || 3.x || 4.x + + rollup@2.79.1: + resolution: + { + integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==, + } + engines: { node: '>=10.0.0' } + hasBin: true + + rollup@4.9.6: + resolution: + { + integrity: sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==, + } + engines: { node: '>=18.0.0', npm: '>=8.0.0' } + hasBin: true + + run-applescript@5.0.0: + resolution: + { + integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==, + } + engines: { node: '>=12' } + + run-async@2.4.1: + resolution: + { + integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==, + } + engines: { node: '>=0.12.0' } + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + + rxjs@7.8.1: + resolution: + { + integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==, + } + + safe-array-concat@1.1.0: + resolution: + { + integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==, + } + engines: { node: '>=0.4' } + + safe-buffer@5.1.2: + resolution: + { + integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, + } + + safe-buffer@5.2.1: + resolution: + { + integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, + } + + safe-regex-test@1.0.2: + resolution: + { + integrity: sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==, + } + engines: { node: '>= 0.4' } + + safer-buffer@2.1.2: + resolution: + { + integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, + } + + sass@1.70.0: + resolution: + { + integrity: sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==, + } + engines: { node: '>=14.0.0' } + hasBin: true + + scheduler@0.23.0: + resolution: + { + integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==, + } + + 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.5.4: + resolution: + { + integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==, + } + engines: { node: '>=10' } + hasBin: true + + set-blocking@2.0.0: + resolution: + { + integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==, + } + + set-function-length@1.2.0: + resolution: + { + integrity: sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==, + } + engines: { node: '>= 0.4' } + + set-function-name@2.0.1: + resolution: + { + integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==, + } + engines: { node: '>= 0.4' } + + setprototypeof@1.2.0: + resolution: + { + integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, + } + + 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' } + + shell-quote@1.8.1: + resolution: + { + integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==, + } + + shiki@0.14.7: + resolution: + { + integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==, + } + + side-channel@1.0.4: + resolution: + { + integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==, + } + + signal-exit@3.0.7: + resolution: + { + integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, + } + + signal-exit@4.1.0: + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, + } + engines: { node: '>=14' } + + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, + } + engines: { node: '>=8' } + + slash@5.1.0: + resolution: + { + integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==, + } + engines: { node: '>=14.16' } + + slice-ansi@4.0.0: + resolution: + { + integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==, + } + engines: { node: '>=10' } + + snake-case@3.0.4: + resolution: + { + integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==, + } + + source-map-js@1.0.2: + resolution: + { + integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, + } + engines: { node: '>=0.10.0' } + + source-map-support@0.5.21: + resolution: + { + integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, + } + + source-map@0.5.7: + resolution: + { + integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==, + } + engines: { node: '>=0.10.0' } + + source-map@0.6.1: + resolution: + { + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, + } + engines: { node: '>=0.10.0' } + + source-map@0.7.4: + resolution: + { + integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==, + } + engines: { node: '>= 8' } + + space-separated-tokens@2.0.2: + resolution: + { + integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==, + } + + spawn-command@0.0.2: + resolution: + { + integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==, + } + + spdx-correct@3.2.0: + resolution: + { + integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==, + } + + spdx-exceptions@2.4.0: + resolution: + { + integrity: sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==, + } + + 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==, + } + + split-on-first@3.0.0: + resolution: + { + integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==, + } + engines: { node: '>=12' } + + split2@3.2.2: + resolution: + { + integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==, + } + + split@1.0.1: + resolution: + { + integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==, + } + + standard-version@9.5.0: + resolution: + { + integrity: sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==, + } + engines: { node: '>=10' } + hasBin: true + + statuses@1.5.0: + resolution: + { + integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==, + } + engines: { node: '>= 0.6' } + + statuses@2.0.1: + resolution: + { + integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==, + } + engines: { node: '>= 0.8' } + + strict-event-emitter@0.5.1: + resolution: + { + integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==, + } + + 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.8: + resolution: + { + integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==, + } + engines: { node: '>= 0.4' } + + string.prototype.trimend@1.0.7: + resolution: + { + integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==, + } + + string.prototype.trimstart@1.0.7: + resolution: + { + integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==, + } + + string_decoder@1.1.1: + resolution: + { + integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==, + } + + string_decoder@1.3.0: + resolution: + { + integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, + } + + stringify-entities@4.0.3: + resolution: + { + integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==, + } + + stringify-package@1.0.1: + resolution: + { + integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==, + } + deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json + + 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-final-newline@3.0.0: + resolution: + { + integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==, + } + engines: { node: '>=12' } + + 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' } + + style-to-js@1.1.10: + resolution: + { + integrity: sha512-VC7MBJa+y0RZhpnLKDPmVRLRswsASLmixkiZ5R8xZpNT9VyjeRzwnXd2pBzAWdgSGv/pCNNH01gPCCUsB9exYg==, + } + + style-to-object@0.4.4: + resolution: + { + integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==, + } + + style-to-object@1.0.5: + resolution: + { + integrity: sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==, + } + + stylelint-config-prettier-scss@1.0.0: + resolution: + { + integrity: sha512-Gr2qLiyvJGKeDk0E/+awNTrZB/UtNVPLqCDOr07na/sLekZwm26Br6yYIeBYz3ulsEcQgs5j+2IIMXCC+wsaQA==, + } + engines: { node: 14.* || 16.* || >= 18 } + hasBin: true + peerDependencies: + stylelint: '>=15.0.0' + + stylelint-config-recommended-scss@14.0.0: + resolution: + { + integrity: sha512-HDvpoOAQ1RpF+sPbDOT2Q2/YrBDEJDnUymmVmZ7mMCeNiFSdhRdyGEimBkz06wsN+HaFwUh249gDR+I9JR7Onw==, + } + engines: { node: '>=18.12.0' } + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.0.2 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-recommended@14.0.0: + resolution: + { + integrity: sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ==, + } + engines: { node: '>=18.12.0' } + peerDependencies: + stylelint: ^16.0.0 + + stylelint-config-standard-scss@13.0.0: + resolution: + { + integrity: sha512-WaLvkP689qSYUpJQPCo30TFJSSc3VzvvoWnrgp+7PpVby5o8fRUY1cZcP0sePZfjrFl9T8caGhcKg0GO34VDiQ==, + } + engines: { node: '>=18.12.0' } + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.1.0 + peerDependenciesMeta: + postcss: + optional: true + + stylelint-config-standard@36.0.0: + resolution: + { + integrity: sha512-3Kjyq4d62bYFp/Aq8PMKDwlgUyPU4nacXsjDLWJdNPRUgpuxALu1KnlAHIj36cdtxViVhXexZij65yM0uNIHug==, + } + engines: { node: '>=18.12.0' } + peerDependencies: + stylelint: ^16.1.0 + + stylelint-scss@6.1.0: + resolution: + { + integrity: sha512-kCfK8TQzthGwb4vaZniZgxRsVbCM4ZckmT1b/H5m4FU3I8Dz0id9llKsy1NMp3XXqC8+OPD4rVKtUbSxXlJb5g==, + } + engines: { node: '>=18.12.0' } + peerDependencies: + stylelint: ^16.0.2 + + stylelint@16.2.0: + resolution: + { + integrity: sha512-gwqU5AkIb52wrAzzn+359S3NIJDMl02TXLUaV2tzA/L6jUdpTwNt+MCxHlc8+Hb2bUHlYVo92YeSIryF2gJthA==, + } + engines: { node: '>=18.12.0' } + hasBin: true + + stylis@4.2.0: + resolution: + { + integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==, + } + + superjson@1.13.3: + resolution: + { + integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==, + } + engines: { node: '>=10' } + + 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-color@8.1.1: + resolution: + { + integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==, + } + engines: { node: '>=10' } + + supports-hyperlinks@3.0.0: + resolution: + { + integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==, + } + engines: { node: '>=14.18' } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, + } + engines: { node: '>= 0.4' } + + svg-parser@2.0.4: + resolution: + { + integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==, + } + + svg-tags@1.0.0: + resolution: + { + integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==, + } + + svgo@3.2.0: + resolution: + { + integrity: sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==, + } + engines: { node: '>=14.0.0' } + hasBin: true + + synckit@0.8.8: + resolution: + { + integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + + systemjs@6.14.3: + resolution: + { + integrity: sha512-hQv45irdhXudAOr8r6SVSpJSGtogdGZUbJBRKCE5nsIS7tsxxvnIHqT4IOPWj+P+HcSzeWzHlGCGpmhPDIKe+w==, + } + + tabbable@6.2.0: + resolution: + { + integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==, + } + + table@6.8.1: + resolution: + { + integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==, + } + engines: { node: '>=10.0.0' } + + terser@5.27.0: + resolution: + { + integrity: sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==, + } + engines: { node: '>=10' } + hasBin: true + + text-extensions@1.9.0: + resolution: + { + integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==, + } + engines: { node: '>=0.10' } + + text-table@0.2.0: + resolution: + { + integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==, + } + + through2@2.0.5: + resolution: + { + integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==, + } + + through2@4.0.2: + resolution: + { + integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==, + } + + through@2.3.8: + resolution: + { + integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, + } + + tiny-invariant@1.3.1: + resolution: + { + integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==, + } + + titleize@3.0.0: + resolution: + { + integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==, + } + engines: { node: '>=12' } + + tmp@0.0.33: + resolution: + { + integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==, + } + engines: { node: '>=0.6.0' } + + 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.1: + resolution: + { + integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==, + } + engines: { node: '>=0.6' } + + tree-kill@1.2.2: + resolution: + { + integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==, + } + hasBin: true + + trim-lines@3.0.1: + resolution: + { + integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==, + } + + trim-newlines@3.0.1: + resolution: + { + integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==, + } + engines: { node: '>=8' } + + trough@2.1.0: + resolution: + { + integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==, + } + + ts-api-utils@1.0.3: + resolution: + { + integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==, + } + engines: { node: '>=16.13.0' } + peerDependencies: + typescript: '>=4.2.0' + + tsconfck@3.0.1: + resolution: + { + integrity: sha512-7ppiBlF3UEddCLeI1JRx5m2Ryq+xk4JrZuq4EuYXykipebaq1dV0Fhgr1hb7CkmHt32QSgOZlcqVLEtHBG4/mg==, + } + engines: { node: ^18 || >=20 } + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tsconfig-paths@3.15.0: + resolution: + { + integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==, + } + + tslib@2.4.0: + resolution: + { + integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==, + } + + tslib@2.6.2: + resolution: + { + integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==, + } + + tsscmp@1.0.6: + resolution: + { + integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==, + } + engines: { node: '>=0.6.x' } + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: '>= 0.8.0' } + + type-fest@0.18.1: + resolution: + { + integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==, + } + 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.21.3: + resolution: + { + integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, + } + 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' } + + type-fest@2.19.0: + resolution: + { + integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==, + } + engines: { node: '>=12.20' } + + type-fest@4.10.1: + resolution: + { + integrity: sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==, + } + engines: { node: '>=16' } + + type-is@1.6.18: + resolution: + { + integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==, + } + engines: { node: '>= 0.6' } + + typed-array-buffer@1.0.0: + resolution: + { + integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==, + } + engines: { node: '>= 0.4' } + + typed-array-byte-length@1.0.0: + resolution: + { + integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==, + } + engines: { node: '>= 0.4' } + + typed-array-byte-offset@1.0.0: + resolution: + { + integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==, + } + engines: { node: '>= 0.4' } + + typed-array-length@1.0.4: + resolution: + { + integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==, + } + + typedarray@0.0.6: + resolution: + { + integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==, + } + + typedoc@0.25.7: + resolution: + { + integrity: sha512-m6A6JjQRg39p2ZVRIN3NKXgrN8vzlHhOS+r9ymUYtcUP/TIQPvWSq7YgE5ZjASfv5Vd5BW5xrir6Gm2XNNcOow==, + } + engines: { node: '>= 16' } + hasBin: true + peerDependencies: + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x + + typesafe-i18n@5.26.2: + resolution: + { + integrity: sha512-2QAriFmiY5JwUAJtG7yufoE/XZ1aFBY++wj7YFS2yo89a3jLBfKoWSdq5JfQYk1V2BS7V2c/u+KEcaCQoE65hw==, + } + hasBin: true + peerDependencies: + typescript: '>=3.5.1' + + typescript-eslint-language-service@5.0.5: + resolution: + { + integrity: sha512-b7gWXpwSTqMVKpPX3WttNZEyVAMKs/2jsHKF79H+qaD6mjzCyU5jboJe/lOZgLJD+QRsXCr0GjIVxvl5kI1NMw==, + } + peerDependencies: + '@typescript-eslint/parser': '>= 5.0.0' + eslint: '>= 8.0.0' + typescript: '>= 4.0.0' + + typescript@5.3.3: + resolution: + { + integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==, + } + engines: { node: '>=14.17' } + hasBin: true + + uglify-js@3.17.4: + resolution: + { + integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==, + } + engines: { node: '>=0.8.0' } + hasBin: true + + unbox-primitive@1.0.2: + resolution: + { + integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==, + } + + undici-types@5.26.5: + resolution: + { + integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==, + } + + unicode-canonical-property-names-ecmascript@2.0.0: + resolution: + { + integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==, + } + engines: { node: '>=4' } + + unicode-match-property-ecmascript@2.0.0: + resolution: + { + integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==, + } + engines: { node: '>=4' } + + unicode-match-property-value-ecmascript@2.1.0: + resolution: + { + integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==, + } + engines: { node: '>=4' } + + unicode-property-aliases-ecmascript@2.1.0: + resolution: + { + integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==, + } + engines: { node: '>=4' } + + unicorn-magic@0.1.0: + resolution: + { + integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==, + } + engines: { node: '>=18' } + + unified@10.1.2: + resolution: + { + integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==, + } + + unified@11.0.4: + resolution: + { + integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==, + } + + unist-util-is@6.0.0: + resolution: + { + integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==, + } + + unist-util-position-from-estree@2.0.0: + resolution: + { + integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==, + } + + unist-util-position@5.0.0: + resolution: + { + integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==, + } + + unist-util-remove-position@5.0.0: + resolution: + { + integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==, + } + + unist-util-stringify-position@3.0.3: + resolution: + { + integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==, + } + + unist-util-stringify-position@4.0.0: + resolution: + { + integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, + } + + unist-util-visit-parents@6.0.1: + resolution: + { + integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==, + } + + unist-util-visit@5.0.0: + resolution: + { + integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==, + } + + untildify@4.0.0: + resolution: + { + integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==, + } + engines: { node: '>=8' } + + update-browserslist-db@1.0.13: + resolution: + { + integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==, + } + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } + + use-breakpoint@4.0.1: + resolution: + { + integrity: sha512-Fa5Duxv3BY3bw8kmj/lmryTETXVUmBQeYJpBgPJ2yJRiIaGVG8rlMNKQE+JS2rywCZHWaggVUz+ytbr7sH/yyg==, + } + peerDependencies: + react: '>=18' + react-dom: '>=18' + + use-deep-compare-effect@1.8.1: + resolution: + { + integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==, + } + engines: { node: '>=10', npm: '>=6' } + peerDependencies: + react: '>=16.13' + + use-sync-external-store@1.2.0: + resolution: + { + integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==, + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + } + + uuid@8.3.2: + resolution: + { + integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==, + } + hasBin: true + + validate-npm-package-license@3.0.4: + resolution: + { + integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==, + } + + vary@1.1.2: + resolution: + { + integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==, + } + engines: { node: '>= 0.8' } + + vfile-location@5.0.2: + resolution: + { + integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==, + } + + vfile-message@3.1.4: + resolution: + { + integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==, + } + + vfile-message@4.0.2: + resolution: + { + integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==, + } + + vfile@5.3.7: + resolution: + { + integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==, + } + + vfile@6.0.1: + resolution: + { + integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==, + } + + victory-vendor@36.8.2: + resolution: + { + integrity: sha512-NfSQi7ISCdBbDpn3b6rg+8RpFZmWIM9mcks48BbogHE2F6h1XKdA34oiCKP5hP1OGvTotDRzsexiJKzrK4Exuw==, + } + + vite-plugin-eslint@1.8.1: + resolution: + { + integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==, + } + peerDependencies: + eslint: '>=7' + vite: '>=2' + + vite-plugin-package-version@1.1.0: + resolution: + { + integrity: sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==, + } + peerDependencies: + vite: '>=2.0.0-beta.69' + + vite-tsconfig-paths@4.3.1: + resolution: + { + integrity: sha512-cfgJwcGOsIxXOLU/nELPny2/LUD/lcf1IbfyeKTv2bsupVbTH/xpFtdQlBmIP1GEK2CjjLxYhFfB+QODFAx5aw==, + } + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@5.0.12: + resolution: + { + integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vscode-oniguruma@1.7.0: + resolution: + { + integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==, + } + + vscode-textmate@8.0.0: + resolution: + { + integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==, + } + + wcwidth@1.0.1: + resolution: + { + integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==, + } + + web-namespaces@2.0.1: + resolution: + { + integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==, + } + + 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.1: + resolution: + { + integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==, + } + + which-module@2.0.1: + resolution: + { + integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==, + } + + which-typed-array@1.1.13: + resolution: + { + integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==, + } + engines: { node: '>= 0.4' } + + which@1.3.1: + resolution: + { + integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==, + } + hasBin: true + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: '>= 8' } + hasBin: true + + widest-line@4.0.1: + resolution: + { + integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==, + } + engines: { node: '>=12' } + + wordwrap@1.0.0: + resolution: + { + integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==, + } + + 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==, + } + + write-file-atomic@5.0.1: + resolution: + { + integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + ws@8.5.0: + resolution: + { + integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==, + } + engines: { node: '>=10.0.0' } + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xtend@4.0.2: + resolution: + { + integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==, + } + engines: { node: '>=0.4' } + + 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==, + } + + yaml@1.10.2: + resolution: + { + integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==, + } + engines: { node: '>= 6' } + + 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-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: '>=12' } + + 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' } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, + } + engines: { node: '>=12' } + + ylru@1.3.2: + resolution: + { + integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==, + } + engines: { node: '>= 4.0.0' } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: '>=10' } + + zod@3.22.4: + resolution: + { + integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==, + } + + zustand@4.5.0: + resolution: + { + integrity: sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==, + } + engines: { node: '>=12.7.0' } + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + + zwitch@2.0.4: + resolution: + { + integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, + } - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true +snapshots: + '@aashutoshrathi/word-wrap@1.2.6': {} - /@adraffy/ens-normalize@1.10.0: - resolution: {integrity: sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q==} - dev: false + '@adraffy/ens-normalize@1.10.0': {} - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} - engines: {node: '>=6.0.0'} + '@ampproject/remapping@2.2.1': dependencies: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 - /@babel/code-frame@7.23.5: - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} - engines: {node: '>=6.9.0'} + '@babel/code-frame@7.23.5': dependencies: '@babel/highlight': 7.23.4 chalk: 2.4.2 - /@babel/compat-data@7.23.5: - resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} - engines: {node: '>=6.9.0'} + '@babel/compat-data@7.23.5': {} - /@babel/core@7.23.9: - resolution: {integrity: sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==} - engines: {node: '>=6.9.0'} + '@babel/core@7.23.9': dependencies: '@ampproject/remapping': 2.2.1 '@babel/code-frame': 7.23.5 @@ -356,32 +9169,22 @@ packages: transitivePeerDependencies: - supports-color - /@babel/generator@7.23.6: - resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} - engines: {node: '>=6.9.0'} + '@babel/generator@7.23.6': dependencies: '@babel/types': 7.23.6 '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 jsesc: 2.5.2 - /@babel/helper-annotate-as-pure@7.22.5: - resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} - engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.22.5': dependencies: '@babel/types': 7.23.6 - dev: false - /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: - resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} - engines: {node: '>=6.9.0'} + '@babel/helper-builder-binary-assignment-operator-visitor@7.22.15': dependencies: '@babel/types': 7.23.6 - dev: false - /@babel/helper-compilation-targets@7.23.6: - resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} - engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.23.6': dependencies: '@babel/compat-data': 7.23.5 '@babel/helper-validator-option': 7.23.5 @@ -389,11 +9192,7 @@ packages: lru-cache: 5.1.1 semver: 6.3.1 - /@babel/helper-create-class-features-plugin@7.23.7(@babel/core@7.23.9): - resolution: {integrity: sha512-xCoqR/8+BoNnXOY7RVSgv6X+o7pmT5q1d+gGcRlXYkI+9B31glE4jeejhKVpA04O1AtzOt7OSQ6VYKP5FcRl9g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-create-class-features-plugin@7.23.7(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-annotate-as-pure': 7.22.5 @@ -405,24 +9204,15 @@ packages: '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 semver: 6.3.1 - dev: false - /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.9): - resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-annotate-as-pure': 7.22.5 regexpu-core: 5.3.2 semver: 6.3.1 - dev: false - /@babel/helper-define-polyfill-provider@0.4.4(@babel/core@7.23.9): - resolution: {integrity: sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-define-polyfill-provider@0.4.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-compilation-targets': 7.23.6 @@ -432,12 +9222,8 @@ packages: resolve: 1.22.8 transitivePeerDependencies: - supports-color - dev: false - /@babel/helper-define-polyfill-provider@0.5.0(@babel/core@7.23.9): - resolution: {integrity: sha512-NovQquuQLAQ5HuyjCz7WQP9MjRj7dx++yspwiyUiGl9ZyadHRSql1HZh5ogRd8W8w6YM6EQ/NTB8rgjLt5W65Q==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + '@babel/helper-define-polyfill-provider@0.5.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-compilation-targets': 7.23.6 @@ -447,43 +9233,27 @@ packages: resolve: 1.22.8 transitivePeerDependencies: - supports-color - dev: false - /@babel/helper-environment-visitor@7.22.20: - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} - engines: {node: '>=6.9.0'} + '@babel/helper-environment-visitor@7.22.20': {} - /@babel/helper-function-name@7.23.0: - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} - engines: {node: '>=6.9.0'} + '@babel/helper-function-name@7.23.0': dependencies: '@babel/template': 7.22.15 '@babel/types': 7.23.6 - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} - engines: {node: '>=6.9.0'} + '@babel/helper-hoist-variables@7.22.5': dependencies: '@babel/types': 7.23.6 - /@babel/helper-member-expression-to-functions@7.23.0: - resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} - engines: {node: '>=6.9.0'} + '@babel/helper-member-expression-to-functions@7.23.0': dependencies: '@babel/types': 7.23.6 - dev: false - /@babel/helper-module-imports@7.22.15: - resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} - engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.22.15': dependencies: '@babel/types': 7.23.6 - /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-environment-visitor': 7.22.20 @@ -492,85 +9262,51 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.20 - /@babel/helper-optimise-call-expression@7.22.5: - resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} - engines: {node: '>=6.9.0'} + '@babel/helper-optimise-call-expression@7.22.5': dependencies: '@babel/types': 7.23.6 - dev: false - /@babel/helper-plugin-utils@7.22.5: - resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} - engines: {node: '>=6.9.0'} - dev: false + '@babel/helper-plugin-utils@7.22.5': {} - /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.9): - resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.22.20 - dev: false - /@babel/helper-replace-supers@7.22.20(@babel/core@7.23.9): - resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/helper-replace-supers@7.22.20(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-member-expression-to-functions': 7.23.0 '@babel/helper-optimise-call-expression': 7.22.5 - dev: false - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} - engines: {node: '>=6.9.0'} + '@babel/helper-simple-access@7.22.5': dependencies: '@babel/types': 7.23.9 - /@babel/helper-skip-transparent-expression-wrappers@7.22.5: - resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} - engines: {node: '>=6.9.0'} + '@babel/helper-skip-transparent-expression-wrappers@7.22.5': dependencies: '@babel/types': 7.23.6 - dev: false - /@babel/helper-split-export-declaration@7.22.6: - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} - engines: {node: '>=6.9.0'} + '@babel/helper-split-export-declaration@7.22.6': dependencies: '@babel/types': 7.23.6 - /@babel/helper-string-parser@7.23.4: - resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} - engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.23.4': {} - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} - engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.22.20': {} - /@babel/helper-validator-option@7.23.5: - resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} - engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.23.5': {} - /@babel/helper-wrap-function@7.22.20: - resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} - engines: {node: '>=6.9.0'} + '@babel/helper-wrap-function@7.22.20': dependencies: '@babel/helper-function-name': 7.23.0 '@babel/template': 7.22.15 '@babel/types': 7.23.6 - dev: false - /@babel/helpers@7.23.9: - resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==} - engines: {node: '>=6.9.0'} + '@babel/helpers@7.23.9': dependencies: '@babel/template': 7.23.9 '@babel/traverse': 7.23.9 @@ -578,322 +9314,177 @@ packages: transitivePeerDependencies: - supports-color - /@babel/highlight@7.23.4: - resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} - engines: {node: '>=6.9.0'} + '@babel/highlight@7.23.4': dependencies: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/parser@7.23.6: - resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==} - engines: {node: '>=6.0.0'} - hasBin: true + '@babel/parser@7.23.6': dependencies: '@babel/types': 7.23.6 - /@babel/parser@7.23.9: - resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} - engines: {node: '>=6.0.0'} - hasBin: true + '@babel/parser@7.23.9': dependencies: '@babel/types': 7.23.9 - /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.9) - dev: false - /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.7(@babel/core@7.23.9): - resolution: {integrity: sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.7(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.9): - resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: false - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.9): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.9): - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.9): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.9): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.9): - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.9): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.9): - resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-async-generator-functions@7.23.7(@babel/core@7.23.9): - resolution: {integrity: sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-async-generator-functions@7.23.7(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.9) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-module-imports': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-block-scoping@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-block-scoping@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-class-static-block@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 + '@babel/plugin-transform-class-static-block@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-classes@7.23.8(@babel/core@7.23.9): - resolution: {integrity: sha512-yAYslGsY1bX6Knmg46RjiCiNSwJKv2IUC8qOdYKqMMr0491SXFhcHqOdRDeCRohOOIzwN/90C6mQ9qAKgrP7dg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-classes@7.23.8(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-annotate-as-pure': 7.22.5 @@ -904,243 +9495,133 @@ packages: '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.9) '@babel/helper-split-export-declaration': 7.22.6 globals: 11.12.0 - dev: false - /@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/template': 7.22.15 - dev: false - /@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-dynamic-import@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-dynamic-import@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-export-namespace-from@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-export-namespace-from@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-for-of@7.23.6(@babel/core@7.23.9): - resolution: {integrity: sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-for-of@7.23.6(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - dev: false - /@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-function-name': 7.23.0 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-json-strings@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-json-strings@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-logical-assignment-operators@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-logical-assignment-operators@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-simple-access': 7.22.5 - dev: false - /@babel/plugin-transform-modules-systemjs@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-systemjs@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-validator-identifier': 7.22.20 - dev: false - /@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.9): - resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-nullish-coalescing-operator@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-nullish-coalescing-operator@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-numeric-separator@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-numeric-separator@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-object-rest-spread@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-object-rest-spread@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/compat-data': 7.23.5 '@babel/core': 7.23.9 @@ -1148,226 +9629,121 @@ packages: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.9) '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-optional-catch-binding@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-optional-catch-binding@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-optional-chaining@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-optional-chaining@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-parameters@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-parameters@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-private-property-in-object@7.23.4(@babel/core@7.23.9): - resolution: {integrity: sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-private-property-in-object@7.23.4(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-annotate-as-pure': 7.22.5 '@babel/helper-create-class-features-plugin': 7.23.7(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.9) - dev: false - /@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-react-jsx-self@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-react-jsx-source@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-source@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 regenerator-transform: 0.15.2 - dev: false - /@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - dev: false - /@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.23.9): - resolution: {integrity: sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 + '@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.9) '@babel/helper-plugin-utils': 7.22.5 - dev: false - /@babel/preset-env@7.23.8(@babel/core@7.23.9): - resolution: {integrity: sha512-lFlpmkApLkEP6woIKprO6DO60RImpatTQKtz4sUcDjVcK8M8mQ4sZsuxaTMNOZf0sqAq/ReYW1ZBHnOQwKpLWA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/preset-env@7.23.8(@babel/core@7.23.9)': dependencies: '@babel/compat-data': 7.23.5 '@babel/core': 7.23.9 @@ -1452,48 +9828,33 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: false - /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.9): - resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@babel/helper-plugin-utils': 7.22.5 '@babel/types': 7.23.6 esutils: 2.0.3 - dev: false - /@babel/regjsgen@0.8.0: - resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} - dev: false + '@babel/regjsgen@0.8.0': {} - /@babel/runtime@7.23.8: - resolution: {integrity: sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==} - engines: {node: '>=6.9.0'} + '@babel/runtime@7.23.8': dependencies: regenerator-runtime: 0.14.1 - /@babel/template@7.22.15: - resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} - engines: {node: '>=6.9.0'} + '@babel/template@7.22.15': dependencies: '@babel/code-frame': 7.23.5 '@babel/parser': 7.23.6 '@babel/types': 7.23.6 - /@babel/template@7.23.9: - resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==} - engines: {node: '>=6.9.0'} + '@babel/template@7.23.9': dependencies: '@babel/code-frame': 7.23.5 '@babel/parser': 7.23.9 '@babel/types': 7.23.9 - /@babel/traverse@7.23.7: - resolution: {integrity: sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==} - engines: {node: '>=6.9.0'} + '@babel/traverse@7.23.7': dependencies: '@babel/code-frame': 7.23.5 '@babel/generator': 7.23.6 @@ -1507,11 +9868,8 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: false - /@babel/traverse@7.23.9: - resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==} - engines: {node: '>=6.9.0'} + '@babel/traverse@7.23.9': dependencies: '@babel/code-frame': 7.23.5 '@babel/generator': 7.23.6 @@ -1526,70 +9884,42 @@ packages: transitivePeerDependencies: - supports-color - /@babel/types@7.23.6: - resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} - engines: {node: '>=6.9.0'} + '@babel/types@7.23.6': dependencies: '@babel/helper-string-parser': 7.23.4 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - /@babel/types@7.23.9: - resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} - engines: {node: '>=6.9.0'} + '@babel/types@7.23.9': dependencies: '@babel/helper-string-parser': 7.23.4 '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 - /@bundled-es-modules/cookie@2.0.0: - resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} + '@bundled-es-modules/cookie@2.0.0': dependencies: cookie: 0.5.0 - dev: false - /@bundled-es-modules/statuses@1.0.1: - resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + '@bundled-es-modules/statuses@1.0.1': dependencies: statuses: 2.0.1 - dev: false - /@csstools/css-parser-algorithms@2.5.0(@csstools/css-tokenizer@2.2.3): - resolution: {integrity: sha512-abypo6m9re3clXA00eu5syw+oaPHbJTPapu9C4pzNsJ4hdZDzushT50Zhu+iIYXgEe1CxnRMn7ngsbV+MLrlpQ==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - '@csstools/css-tokenizer': ^2.2.3 + '@csstools/css-parser-algorithms@2.5.0(@csstools/css-tokenizer@2.2.3)': dependencies: '@csstools/css-tokenizer': 2.2.3 - dev: true - /@csstools/css-tokenizer@2.2.3: - resolution: {integrity: sha512-pp//EvZ9dUmGuGtG1p+n17gTHEOqu9jO+FiCUjNN3BDmyhdA2Jq9QsVeR7K8/2QCK17HSsioPlTW9ZkzoWb3Lg==} - engines: {node: ^14 || ^16 || >=18} - dev: true + '@csstools/css-tokenizer@2.2.3': {} - /@csstools/media-query-list-parser@2.1.7(@csstools/css-parser-algorithms@2.5.0)(@csstools/css-tokenizer@2.2.3): - resolution: {integrity: sha512-lHPKJDkPUECsyAvD60joYfDmp8UERYxHGkFfyLJFTVK/ERJe0sVlIFLXU5XFxdjNDTerp5L4KeaKG+Z5S94qxQ==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - '@csstools/css-parser-algorithms': ^2.5.0 - '@csstools/css-tokenizer': ^2.2.3 + '@csstools/media-query-list-parser@2.1.7(@csstools/css-parser-algorithms@2.5.0(@csstools/css-tokenizer@2.2.3))(@csstools/css-tokenizer@2.2.3)': dependencies: '@csstools/css-parser-algorithms': 2.5.0(@csstools/css-tokenizer@2.2.3) '@csstools/css-tokenizer': 2.2.3 - dev: true - /@csstools/selector-specificity@3.0.1(postcss-selector-parser@6.0.15): - resolution: {integrity: sha512-NPljRHkq4a14YzZ3YD406uaxh7s0g6eAq3L9aLOWywoqe8PkYamAvtsh7KNX6c++ihDrJ0RiU+/z7rGnhlZ5ww==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - postcss-selector-parser: ^6.0.13 + '@csstools/selector-specificity@3.0.1(postcss-selector-parser@6.0.15)': dependencies: postcss-selector-parser: 6.0.15 - dev: true - /@emotion/babel-plugin@11.11.0: - resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} + '@emotion/babel-plugin@11.11.0': dependencies: '@babel/helper-module-imports': 7.22.15 '@babel/runtime': 7.23.8 @@ -1602,54 +9932,32 @@ packages: find-root: 1.1.0 source-map: 0.5.7 stylis: 4.2.0 - dev: true - /@emotion/cache@11.11.0: - resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==} + '@emotion/cache@11.11.0': dependencies: '@emotion/memoize': 0.8.1 '@emotion/sheet': 1.2.2 '@emotion/utils': 1.2.1 '@emotion/weak-memoize': 0.3.1 stylis: 4.2.0 - dev: true - /@emotion/hash@0.9.1: - resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} - dev: true + '@emotion/hash@0.9.1': {} - /@emotion/is-prop-valid@0.8.8: - resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==} - requiresBuild: true + '@emotion/is-prop-valid@0.8.8': dependencies: '@emotion/memoize': 0.7.4 - dev: false optional: true - /@emotion/is-prop-valid@1.2.1: - resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==} + '@emotion/is-prop-valid@1.2.1': dependencies: '@emotion/memoize': 0.8.1 - dev: true - /@emotion/memoize@0.7.4: - resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} - requiresBuild: true - dev: false + '@emotion/memoize@0.7.4': optional: true - /@emotion/memoize@0.8.1: - resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} - dev: true + '@emotion/memoize@0.8.1': {} - /@emotion/react@11.11.3(@types/react@18.2.48)(react@18.2.0): - resolution: {integrity: sha512-Cnn0kuq4DoONOMcnoVsTOR8E+AdnKFf//6kUWc4LCdnxj31pZWn7rIULd6Y7/Js1PiPHzn7SKCM9vB/jBni8eA==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true + '@emotion/react@11.11.3(@types/react@18.2.48)(react@18.2.0)': dependencies: '@babel/runtime': 7.23.8 '@emotion/babel-plugin': 11.11.0 @@ -1658,34 +9966,22 @@ packages: '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@emotion/utils': 1.2.1 '@emotion/weak-memoize': 0.3.1 - '@types/react': 18.2.48 hoist-non-react-statics: 3.3.2 react: 18.2.0 - dev: true + optionalDependencies: + '@types/react': 18.2.48 - /@emotion/serialize@1.1.3: - resolution: {integrity: sha512-iD4D6QVZFDhcbH0RAG1uVu1CwVLMWUkCvAqqlewO/rxf8+87yIBAlt4+AxMiiKPLs5hFc0owNk/sLLAOROw3cA==} + '@emotion/serialize@1.1.3': dependencies: '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/unitless': 0.8.1 '@emotion/utils': 1.2.1 csstype: 3.1.3 - dev: true - /@emotion/sheet@1.2.2: - resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} - dev: true + '@emotion/sheet@1.2.2': {} - /@emotion/styled@11.11.0(@emotion/react@11.11.3)(@types/react@18.2.48)(react@18.2.0): - resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==} - peerDependencies: - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true + '@emotion/styled@11.11.0(@emotion/react@11.11.3(@types/react@18.2.48)(react@18.2.0))(@types/react@18.2.48)(react@18.2.0)': dependencies: '@babel/runtime': 7.23.8 '@emotion/babel-plugin': 11.11.0 @@ -1694,232 +9990,97 @@ packages: '@emotion/serialize': 1.1.3 '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) '@emotion/utils': 1.2.1 - '@types/react': 18.2.48 react: 18.2.0 - dev: true + optionalDependencies: + '@types/react': 18.2.48 - /@emotion/unitless@0.8.1: - resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} - dev: true + '@emotion/unitless@0.8.1': {} - /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): - resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} - peerDependencies: - react: '>=16.8.0' + '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0)': dependencies: react: 18.2.0 - dev: true - /@emotion/utils@1.2.1: - resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} - dev: true + '@emotion/utils@1.2.1': {} - /@emotion/weak-memoize@0.3.1: - resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} - dev: true + '@emotion/weak-memoize@0.3.1': {} - /@esbuild/aix-ppc64@0.19.12: - resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - requiresBuild: true + '@esbuild/aix-ppc64@0.19.12': optional: true - /@esbuild/android-arm64@0.19.12: - resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true + '@esbuild/android-arm64@0.19.12': optional: true - /@esbuild/android-arm@0.19.12: - resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true + '@esbuild/android-arm@0.19.12': optional: true - /@esbuild/android-x64@0.19.12: - resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true + '@esbuild/android-x64@0.19.12': optional: true - /@esbuild/darwin-arm64@0.19.12: - resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true + '@esbuild/darwin-arm64@0.19.12': optional: true - /@esbuild/darwin-x64@0.19.12: - resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true + '@esbuild/darwin-x64@0.19.12': optional: true - /@esbuild/freebsd-arm64@0.19.12: - resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true + '@esbuild/freebsd-arm64@0.19.12': optional: true - /@esbuild/freebsd-x64@0.19.12: - resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true + '@esbuild/freebsd-x64@0.19.12': optional: true - /@esbuild/linux-arm64@0.19.12: - resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true + '@esbuild/linux-arm64@0.19.12': optional: true - /@esbuild/linux-arm@0.19.12: - resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true + '@esbuild/linux-arm@0.19.12': optional: true - /@esbuild/linux-ia32@0.19.12: - resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true + '@esbuild/linux-ia32@0.19.12': optional: true - /@esbuild/linux-loong64@0.19.12: - resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true + '@esbuild/linux-loong64@0.19.12': optional: true - /@esbuild/linux-mips64el@0.19.12: - resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true + '@esbuild/linux-mips64el@0.19.12': optional: true - /@esbuild/linux-ppc64@0.19.12: - resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true + '@esbuild/linux-ppc64@0.19.12': optional: true - /@esbuild/linux-riscv64@0.19.12: - resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true + '@esbuild/linux-riscv64@0.19.12': optional: true - /@esbuild/linux-s390x@0.19.12: - resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true + '@esbuild/linux-s390x@0.19.12': optional: true - /@esbuild/linux-x64@0.19.12: - resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true + '@esbuild/linux-x64@0.19.12': optional: true - /@esbuild/netbsd-x64@0.19.12: - resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true + '@esbuild/netbsd-x64@0.19.12': optional: true - /@esbuild/openbsd-x64@0.19.12: - resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true + '@esbuild/openbsd-x64@0.19.12': optional: true - /@esbuild/sunos-x64@0.19.12: - resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true + '@esbuild/sunos-x64@0.19.12': optional: true - /@esbuild/win32-arm64@0.19.12: - resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true + '@esbuild/win32-arm64@0.19.12': optional: true - /@esbuild/win32-ia32@0.19.12: - resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true + '@esbuild/win32-ia32@0.19.12': optional: true - /@esbuild/win32-x64@0.19.12: - resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true + '@esbuild/win32-x64@0.19.12': optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.56.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/eslint-utils@4.4.0(eslint@8.56.0)': dependencies: eslint: 8.56.0 eslint-visitor-keys: 3.4.3 - dev: true - /@eslint-community/regexpp@4.10.0: - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true + '@eslint-community/regexpp@4.10.0': {} - /@eslint/eslintrc@2.1.4: - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 debug: 4.3.4 @@ -1932,172 +10093,106 @@ packages: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color - dev: true - /@eslint/js@8.56.0: - resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + '@eslint/js@8.56.0': {} - /@floating-ui/core@1.6.0: - resolution: {integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==} + '@floating-ui/core@1.6.0': dependencies: '@floating-ui/utils': 0.2.1 - dev: false - /@floating-ui/dom@1.6.0: - resolution: {integrity: sha512-SZ0BEXzsaaS6THZfZJUcAobbZTD+MvfGM42bxgeg0Tnkp4/an/avqwAXiVLsFtIBZtfsx3Ymvwx0+KnnhdA/9g==} + '@floating-ui/dom@1.6.0': dependencies: '@floating-ui/core': 1.6.0 '@floating-ui/utils': 0.2.1 - dev: false - /@floating-ui/react-dom@2.0.7(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-B5GJxKUyPcGsvE1vua+Abvw0t6zVMyTbtG+Jk7BoI4hfc5Ahv50dstRIAn0nS0274kR9gnKwxIXyGA8EzBZJrA==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' + '@floating-ui/react-dom@2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@floating-ui/dom': 1.6.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: false - /@floating-ui/react@0.26.7(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-0uMI9IBJBPPt8N+8uRg4gazJvQReWTu/fVUHHLfAOuy1WB6f242jtjWm52hLJG8nzuZVuU+2crW4lJbJQoqeIA==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' + '@floating-ui/react@0.26.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: - '@floating-ui/react-dom': 2.0.7(react-dom@18.2.0)(react@18.2.0) + '@floating-ui/react-dom': 2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@floating-ui/utils': 0.2.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) tabbable: 6.2.0 - dev: false - /@floating-ui/utils@0.2.1: - resolution: {integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==} - dev: false + '@floating-ui/utils@0.2.1': {} - /@github/webauthn-json@2.1.1: - resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==} - hasBin: true - dev: false + '@github/webauthn-json@2.1.1': {} - /@hookform/devtools@4.3.1(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-CrWxEoHQZaOXJZVQ8KBgOuAa8p2LI8M0DAN5GTRTmdCieRwFVjVDEmuTAVazWVRRkpEQSgSt3KYp7VmmqXdEnw==} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 + '@hookform/devtools@4.3.1(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@emotion/react': 11.11.3(@types/react@18.2.48)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.3)(@types/react@18.2.48)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.3(@types/react@18.2.48)(react@18.2.0))(@types/react@18.2.48)(react@18.2.0) '@types/lodash': 4.14.202 little-state-machine: 4.8.0(react@18.2.0) lodash: 4.17.21 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-simple-animate: 3.5.2(react-dom@18.2.0) + react-simple-animate: 3.5.2(react-dom@18.2.0(react@18.2.0)) use-deep-compare-effect: 1.8.1(react@18.2.0) uuid: 8.3.2 transitivePeerDependencies: - '@types/react' - dev: true - /@hookform/resolvers@3.3.4(react-hook-form@7.49.3): - resolution: {integrity: sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==} - peerDependencies: - react-hook-form: ^7.0.0 + '@hookform/resolvers@3.3.4(react-hook-form@7.49.3(react@18.2.0))': dependencies: react-hook-form: 7.49.3(react@18.2.0) - dev: false - /@humanwhocodes/config-array@0.11.14: - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} + '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.2 debug: 4.3.4 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - dev: true - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true + '@humanwhocodes/module-importer@1.0.1': {} - /@humanwhocodes/object-schema@2.0.2: - resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} - dev: true + '@humanwhocodes/object-schema@2.0.2': {} - /@hutson/parse-repository-url@3.0.2: - resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} - engines: {node: '>=6.9.0'} - dev: true + '@hutson/parse-repository-url@3.0.2': {} - /@isaacs/cliui@8.0.2: - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 + string-width-cjs: string-width@4.2.3 strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 + strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true + wrap-ansi-cjs: wrap-ansi@7.0.0 - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.3': dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/trace-mapping': 0.3.22 - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} - engines: {node: '>=6.0.0'} + '@jridgewell/resolve-uri@3.1.1': {} - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} + '@jridgewell/set-array@1.1.2': {} - /@jridgewell/source-map@0.3.5: - resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + '@jridgewell/source-map@0.3.5': dependencies: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.4.15': {} - /@jridgewell/trace-mapping@0.3.22: - resolution: {integrity: sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==} + '@jridgewell/trace-mapping@0.3.22': dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - - /@ladle/react-context@1.0.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-xVQ8siyOEQG6e4Knibes1uA3PTyXnqiMmfSmd5pIbkzeDty8NCBtYHhTXSlfmcDNEsw/G8OzNWo4VbyQAVDl2A==} - peerDependencies: - react: '>=16.14.0' - react-dom: '>=16.14.0' + + '@ladle/react-context@1.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: false - /@ladle/react@4.0.2(@types/node@20.11.7)(@types/react@18.2.48)(react-dom@18.2.0)(react@18.2.0)(sass@1.70.0)(terser@5.27.0)(typescript@5.3.3): - resolution: {integrity: sha512-SnYniR/U7kJX1Zh199jhjxqiFa5e5eA8chuX6uYEZYAUtCCY/hQqGr7/7Grr0j6Q4FYu9iQyyV2K1NJKDdUZIw==} - engines: {node: '>=18.0.0'} - hasBin: true - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' + '@ladle/react@4.0.2(@types/node@20.11.7)(@types/react@18.2.48)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.70.0)(terser@5.27.0)(typescript@5.3.3)': dependencies: '@babel/code-frame': 7.23.5 '@babel/core': 7.23.9 @@ -2106,11 +10201,11 @@ packages: '@babel/template': 7.22.15 '@babel/traverse': 7.23.7 '@babel/types': 7.23.6 - '@ladle/react-context': 1.0.1(react-dom@18.2.0)(react@18.2.0) + '@ladle/react-context': 1.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@mdx-js/mdx': 3.0.0 '@mdx-js/react': 3.0.0(@types/react@18.2.48)(react@18.2.0) - '@vitejs/plugin-react': 4.2.1(vite@5.0.12) - '@vitejs/plugin-react-swc': 3.5.0(vite@5.0.12) + '@vitejs/plugin-react': 4.2.1(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)) + '@vitejs/plugin-react-swc': 3.5.0(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)) axe-core: 4.8.3 boxen: 7.1.1 chokidar: 3.5.3 @@ -2131,7 +10226,7 @@ packages: query-string: 8.1.0 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-hotkeys-hook: 4.4.4(react-dom@18.2.0)(react@18.2.0) + react-hotkeys-hook: 4.4.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react-inspector: 6.0.2(react@18.2.0) rehype-class-names: 1.0.14 rehype-raw: 7.0.0 @@ -2139,7 +10234,7 @@ packages: source-map: 0.7.4 vfile: 6.0.1 vite: 5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0) - vite-tsconfig-paths: 4.3.1(typescript@5.3.3)(vite@5.0.12) + vite-tsconfig-paths: 4.3.1(typescript@5.3.3)(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)) transitivePeerDependencies: - '@swc/helpers' - '@types/node' @@ -2152,10 +10247,8 @@ packages: - supports-color - terser - typescript - dev: false - /@mdx-js/mdx@3.0.0: - resolution: {integrity: sha512-Icm0TBKBLYqroYbNW3BPnzMGn+7mwpQOK310aZ7+fkCtiU3aqv2cdcX+nd0Ydo3wI5Rx8bX2Z2QmGb/XcAClCw==} + '@mdx-js/mdx@3.0.0': dependencies: '@types/estree': 1.0.5 '@types/estree-jsx': 1.0.3 @@ -2182,32 +10275,18 @@ packages: vfile: 6.0.1 transitivePeerDependencies: - supports-color - dev: false - /@mdx-js/react@3.0.0(@types/react@18.2.48)(react@18.2.0): - resolution: {integrity: sha512-nDctevR9KyYFyV+m+/+S4cpzCWHqj+iHDHq3QrsWezcC+B17uZdIWgCguESUkwFhM3n/56KxWVE3V6EokrmONQ==} - peerDependencies: - '@types/react': '>=16' - react: '>=16' + '@mdx-js/react@3.0.0(@types/react@18.2.48)(react@18.2.0)': dependencies: '@types/mdx': 2.0.10 '@types/react': 18.2.48 react: 18.2.0 - dev: false - /@metamask/detect-provider@2.0.0: - resolution: {integrity: sha512-sFpN+TX13E9fdBDh9lvQeZdJn4qYoRb/6QF2oZZK/Pn559IhCFacPMU1rMuqyXoFQF3JSJfii2l98B87QDPeCQ==} - engines: {node: '>=14.0.0'} - dev: false + '@metamask/detect-provider@2.0.0': {} - /@mswjs/cookies@1.1.0: - resolution: {integrity: sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==} - engines: {node: '>=18'} - dev: false + '@mswjs/cookies@1.1.0': {} - /@mswjs/interceptors@0.25.14: - resolution: {integrity: sha512-2dnIxl+obqIqjoPXTFldhe6pcdOrqiz+GcLaQQ6hmL02OldAF7nIC+rUgTWm+iF6lvmyCVhFFqbgbapNhR8eag==} - engines: {node: '>=18'} + '@mswjs/interceptors@0.25.14': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -2215,317 +10294,160 @@ packages: is-node-process: 1.2.0 outvariant: 1.4.2 strict-event-emitter: 0.5.1 - dev: false - /@noble/curves@1.2.0: - resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} + '@noble/curves@1.2.0': dependencies: '@noble/hashes': 1.3.2 - dev: false - /@noble/hashes@1.3.2: - resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} - engines: {node: '>= 16'} - dev: false + '@noble/hashes@1.3.2': {} - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} + '@nodelib/fs.stat@2.0.5': {} - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.16.0 - /@open-draft/deferred-promise@2.2.0: - resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - dev: false + '@open-draft/deferred-promise@2.2.0': {} - /@open-draft/logger@0.3.0: - resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + '@open-draft/logger@0.3.0': dependencies: is-node-process: 1.2.0 outvariant: 1.4.2 - dev: false - /@open-draft/until@2.1.0: - resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - dev: false + '@open-draft/until@2.1.0': {} - /@pkgjs/parseargs@0.11.0: - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - requiresBuild: true - dev: true + '@pkgjs/parseargs@0.11.0': optional: true - /@pkgr/core@0.1.1: - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - dev: true + '@pkgr/core@0.1.1': {} - /@reach/observe-rect@1.2.0: - resolution: {integrity: sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==} - dev: false + '@reach/observe-rect@1.2.0': {} - /@react-rxjs/core@0.10.7(react@18.2.0)(rxjs@7.8.1): - resolution: {integrity: sha512-dornp8pUs9OcdqFKKRh9+I2FVe21gWufNun6RYU1ddts7kUy9i4Thvl0iqcPFbGY61cJQMAJF7dxixWMSD/A/A==} - peerDependencies: - react: '>=16.8.0' - rxjs: '>=7' + '@react-rxjs/core@0.10.7(react@18.2.0)(rxjs@7.8.1)': dependencies: '@rx-state/core': 0.1.4(rxjs@7.8.1) react: 18.2.0 rxjs: 7.8.1 use-sync-external-store: 1.2.0(react@18.2.0) - dev: false - /@remix-run/router@1.14.2: - resolution: {integrity: sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==} - engines: {node: '>=14.0.0'} - dev: false + '@remix-run/router@1.14.2': {} - /@rollup/pluginutils@4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} + '@rollup/pluginutils@4.2.1': dependencies: estree-walker: 2.0.2 picomatch: 2.3.1 - dev: true - /@rollup/rollup-android-arm-eabi@4.9.6: - resolution: {integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==} - cpu: [arm] - os: [android] - requiresBuild: true + '@rollup/rollup-android-arm-eabi@4.9.6': optional: true - /@rollup/rollup-android-arm64@4.9.6: - resolution: {integrity: sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==} - cpu: [arm64] - os: [android] - requiresBuild: true + '@rollup/rollup-android-arm64@4.9.6': optional: true - /@rollup/rollup-darwin-arm64@4.9.6: - resolution: {integrity: sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==} - cpu: [arm64] - os: [darwin] - requiresBuild: true + '@rollup/rollup-darwin-arm64@4.9.6': optional: true - /@rollup/rollup-darwin-x64@4.9.6: - resolution: {integrity: sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==} - cpu: [x64] - os: [darwin] - requiresBuild: true + '@rollup/rollup-darwin-x64@4.9.6': optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.9.6: - resolution: {integrity: sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==} - cpu: [arm] - os: [linux] - requiresBuild: true + '@rollup/rollup-linux-arm-gnueabihf@4.9.6': optional: true - /@rollup/rollup-linux-arm64-gnu@4.9.6: - resolution: {integrity: sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true + '@rollup/rollup-linux-arm64-gnu@4.9.6': optional: true - /@rollup/rollup-linux-arm64-musl@4.9.6: - resolution: {integrity: sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true + '@rollup/rollup-linux-arm64-musl@4.9.6': optional: true - /@rollup/rollup-linux-riscv64-gnu@4.9.6: - resolution: {integrity: sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==} - cpu: [riscv64] - os: [linux] - requiresBuild: true + '@rollup/rollup-linux-riscv64-gnu@4.9.6': optional: true - /@rollup/rollup-linux-x64-gnu@4.9.6: - resolution: {integrity: sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==} - cpu: [x64] - os: [linux] - requiresBuild: true + '@rollup/rollup-linux-x64-gnu@4.9.6': optional: true - /@rollup/rollup-linux-x64-musl@4.9.6: - resolution: {integrity: sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==} - cpu: [x64] - os: [linux] - requiresBuild: true + '@rollup/rollup-linux-x64-musl@4.9.6': optional: true - /@rollup/rollup-win32-arm64-msvc@4.9.6: - resolution: {integrity: sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==} - cpu: [arm64] - os: [win32] - requiresBuild: true + '@rollup/rollup-win32-arm64-msvc@4.9.6': optional: true - /@rollup/rollup-win32-ia32-msvc@4.9.6: - resolution: {integrity: sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==} - cpu: [ia32] - os: [win32] - requiresBuild: true + '@rollup/rollup-win32-ia32-msvc@4.9.6': optional: true - /@rollup/rollup-win32-x64-msvc@4.9.6: - resolution: {integrity: sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==} - cpu: [x64] - os: [win32] - requiresBuild: true + '@rollup/rollup-win32-x64-msvc@4.9.6': optional: true - /@rx-state/core@0.1.4(rxjs@7.8.1): - resolution: {integrity: sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ==} - peerDependencies: - rxjs: '>=7' + '@rx-state/core@0.1.4(rxjs@7.8.1)': dependencies: rxjs: 7.8.1 - dev: false - /@sindresorhus/merge-streams@1.0.0: - resolution: {integrity: sha512-rUV5WyJrJLoloD4NDN1V1+LDMDWOa4OTsT4yYJwQNpTU6FWxkxHpL7eu4w+DmiH8x/EAM1otkPE1+LaspIbplw==} - engines: {node: '>=18'} - dev: false + '@sindresorhus/merge-streams@1.0.0': {} - /@stablelib/base64@1.0.1: - resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} - dev: false + '@stablelib/base64@1.0.1': {} - /@stablelib/binary@1.0.1: - resolution: {integrity: sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==} + '@stablelib/binary@1.0.1': dependencies: '@stablelib/int': 1.0.1 - dev: false - /@stablelib/bytes@1.0.1: - resolution: {integrity: sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ==} - dev: false + '@stablelib/bytes@1.0.1': {} - /@stablelib/int@1.0.1: - resolution: {integrity: sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==} - dev: false + '@stablelib/int@1.0.1': {} - /@stablelib/keyagreement@1.0.1: - resolution: {integrity: sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg==} + '@stablelib/keyagreement@1.0.1': dependencies: '@stablelib/bytes': 1.0.1 - dev: false - /@stablelib/random@1.0.2: - resolution: {integrity: sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==} + '@stablelib/random@1.0.2': dependencies: '@stablelib/binary': 1.0.1 '@stablelib/wipe': 1.0.1 - dev: false - /@stablelib/wipe@1.0.1: - resolution: {integrity: sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==} - dev: false + '@stablelib/wipe@1.0.1': {} - /@stablelib/x25519@1.0.3: - resolution: {integrity: sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==} + '@stablelib/x25519@1.0.3': dependencies: '@stablelib/keyagreement': 1.0.1 '@stablelib/random': 1.0.2 '@stablelib/wipe': 1.0.1 - dev: false - /@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.23.9): - resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: true - /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.23.9): - resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: true - /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.23.9): - resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: true - /@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.23.9): - resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: true - /@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.23.9): - resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: true - /@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.23.9): - resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: true - /@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.23.9): - resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: true - /@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.23.9): - resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} - engines: {node: '>=12'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 - dev: true - /@svgr/babel-preset@8.1.0(@babel/core@7.23.9): - resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 + '@svgr/babel-preset@8.1.0(@babel/core@7.23.9)': dependencies: '@babel/core': 7.23.9 '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.23.9) @@ -2536,17 +10458,13 @@ packages: '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.23.9) '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.23.9) '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.23.9) - dev: true - /@svgr/cli@8.1.0(typescript@5.3.3): - resolution: {integrity: sha512-SnlaLspB610XFXvs3PmhzViHErsXp0yIy4ERyZlHDlO1ro2iYtHMWYk2mztdLD/lBjiA4ZXe4RePON3qU/Tc4A==} - engines: {node: '>=14'} - hasBin: true + '@svgr/cli@8.1.0(typescript@5.3.3)': dependencies: '@svgr/core': 8.1.0(typescript@5.3.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0) - '@svgr/plugin-prettier': 8.1.0(@svgr/core@8.1.0) - '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0)(typescript@5.3.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.3.3)) + '@svgr/plugin-prettier': 8.1.0(@svgr/core@8.1.0(typescript@5.3.3)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.3.3))(typescript@5.3.3) camelcase: 6.3.0 chalk: 4.1.2 commander: 9.5.0 @@ -2556,11 +10474,8 @@ packages: transitivePeerDependencies: - supports-color - typescript - dev: true - /@svgr/core@8.1.0(typescript@5.3.3): - resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} - engines: {node: '>=14'} + '@svgr/core@8.1.0(typescript@5.3.3)': dependencies: '@babel/core': 7.23.9 '@svgr/babel-preset': 8.1.0(@babel/core@7.23.9) @@ -2570,21 +10485,13 @@ packages: transitivePeerDependencies: - supports-color - typescript - dev: true - /@svgr/hast-util-to-babel-ast@8.0.0: - resolution: {integrity: sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==} - engines: {node: '>=14'} + '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: '@babel/types': 7.23.6 entities: 4.5.0 - dev: true - /@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0): - resolution: {integrity: sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==} - engines: {node: '>=14'} - peerDependencies: - '@svgr/core': '*' + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.3.3))': dependencies: '@babel/core': 7.23.9 '@svgr/babel-preset': 8.1.0(@babel/core@7.23.9) @@ -2593,24 +10500,14 @@ packages: svg-parser: 2.0.4 transitivePeerDependencies: - supports-color - dev: true - /@svgr/plugin-prettier@8.1.0(@svgr/core@8.1.0): - resolution: {integrity: sha512-o4/uFI8G64tAjBZ4E7gJfH+VP7Qi3T0+M4WnIsP91iFnGPqs5WvPDkpZALXPiyWEtzfYs1Rmwy1Zdfu8qoZuKw==} - engines: {node: '>=14'} - peerDependencies: - '@svgr/core': '*' + '@svgr/plugin-prettier@8.1.0(@svgr/core@8.1.0(typescript@5.3.3))': dependencies: '@svgr/core': 8.1.0(typescript@5.3.3) deepmerge: 4.3.1 prettier: 2.8.8 - dev: true - /@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0)(typescript@5.3.3): - resolution: {integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==} - engines: {node: '>=14'} - peerDependencies: - '@svgr/core': '*' + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.3.3))(typescript@5.3.3)': dependencies: '@svgr/core': 8.1.0(typescript@5.3.3) cosmiconfig: 8.3.6(typescript@5.3.3) @@ -2618,97 +10515,38 @@ packages: svgo: 3.2.0 transitivePeerDependencies: - typescript - dev: true - /@swc/core-darwin-arm64@1.3.106: - resolution: {integrity: sha512-XYcbViNyHnnm7RWOAO1YipMmthM7m2aXF32b0y+JMLYFBEyFpjVX9btLkzeL7wRx/5B3I35yJNhE+xyx0Q1Gkw==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true + '@swc/core-darwin-arm64@1.3.106': optional: true - /@swc/core-darwin-x64@1.3.106: - resolution: {integrity: sha512-YKDPhUdfuwhmOUS9+CaIwl/0Tp+f1b73BH2EIESuxSNsogZf18a8HQ8O0fQEwdiwmA5LEqw47cj+kfOWV/0+kw==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - requiresBuild: true + '@swc/core-darwin-x64@1.3.106': optional: true - /@swc/core-linux-arm-gnueabihf@1.3.106: - resolution: {integrity: sha512-bHxxJXogvFfocLL5inZxxtx/x/WgKozigp80Vbx0viac1fPDJrqKBw2X4MzpMiuTRAGVQ03jJI6pDwbSBf+yDw==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - requiresBuild: true + '@swc/core-linux-arm-gnueabihf@1.3.106': optional: true - /@swc/core-linux-arm64-gnu@1.3.106: - resolution: {integrity: sha512-c7jue++CHLgtpeaakEukoCLT9eNrImizbleE9Y7Is8CHqLq/7DG4s+7ma9DFKXIzW2MpTg9byIEQfpqSphVW6A==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - requiresBuild: true + '@swc/core-linux-arm64-gnu@1.3.106': optional: true - /@swc/core-linux-arm64-musl@1.3.106: - resolution: {integrity: sha512-51EaC3Q8qAhLtWVnAVqoYX/gk3tK31cCBzUpwCcmhianhEBM2/WtKRAS4MqPhE8VVZuN3WjO2c2JaF2mX0yuoA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - requiresBuild: true + '@swc/core-linux-arm64-musl@1.3.106': optional: true - /@swc/core-linux-x64-gnu@1.3.106: - resolution: {integrity: sha512-tOUi8BB6jAeCXgx7ESLNnX7nrbMVKQ/XajK77v7Ad4SXf9HYArnimBJpXUUyVFJTXLSv4e6c7s6XHHqXb5Lwcg==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - requiresBuild: true + '@swc/core-linux-x64-gnu@1.3.106': optional: true - /@swc/core-linux-x64-musl@1.3.106: - resolution: {integrity: sha512-binLw4Lbd83NPy4/m/teH2nbaifxveSD+sKDvpxywRbvYW2I0w/iCBpUBcbnl16TQF4TPOGpq5YwG9lVxPVw5g==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - requiresBuild: true + '@swc/core-linux-x64-musl@1.3.106': optional: true - /@swc/core-win32-arm64-msvc@1.3.106: - resolution: {integrity: sha512-n4ttBWr8tM7DPzwcEOIBTyTMHZTzCmbic/HTtxEsPyMAf/Daen+yrTKzjPP6k2usfSrjkxA780RSJJxI1N8r2w==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - requiresBuild: true + '@swc/core-win32-arm64-msvc@1.3.106': optional: true - /@swc/core-win32-ia32-msvc@1.3.106: - resolution: {integrity: sha512-GhDNIwxE5FhkujESI6h/4ysT3wxwmrzTUlZYaR8rRui6a6SdX9feIPUHPEE5o5hpyp+xqlmvRxKkRxOnwsq8iA==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - requiresBuild: true + '@swc/core-win32-ia32-msvc@1.3.106': optional: true - /@swc/core-win32-x64-msvc@1.3.106: - resolution: {integrity: sha512-2M6yWChuMS1+/MPo3Dor0SOMkvmiugonWlzsZBAu/oZboH2xKrHSRv7brsBujb2Oe47r+NsbV+vq9tnnP9Vl1Q==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - requiresBuild: true + '@swc/core-win32-x64-msvc@1.3.106': optional: true - /@swc/core@1.3.106: - resolution: {integrity: sha512-++QPSPkFq2qELYVScxNHJC42hKQChjiTWS2P0QQ5JWT4NHb9lmNSfrc1ylFIyImwRnxsW2MTBALLYLf95EFAsg==} - engines: {node: '>=10'} - requiresBuild: true - peerDependencies: - '@swc/helpers': ^0.5.0 - peerDependenciesMeta: - '@swc/helpers': - optional: true + '@swc/core@1.3.106': dependencies: '@swc/counter': 0.1.2 '@swc/types': 0.1.5 @@ -2724,331 +10562,196 @@ packages: '@swc/core-win32-ia32-msvc': 1.3.106 '@swc/core-win32-x64-msvc': 1.3.106 - /@swc/counter@0.1.2: - resolution: {integrity: sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==} + '@swc/counter@0.1.2': {} - /@swc/types@0.1.5: - resolution: {integrity: sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==} + '@swc/types@0.1.5': {} - /@tanstack/match-sorter-utils@8.11.7: - resolution: {integrity: sha512-4PUKgaaFpiB7MK406N5VAiLu2VUhDumojGWhEC8kNQ767RGU2vsJDI7Xp4D8lMBzijqswRWz3U8ioa2zUKnFeQ==} - engines: {node: '>=12'} + '@tanstack/match-sorter-utils@8.11.7': dependencies: remove-accents: 0.4.2 - dev: true - /@tanstack/query-core@4.36.1: - resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==} + '@tanstack/query-core@4.36.1': {} - /@tanstack/react-query-devtools@4.36.1(@tanstack/react-query@4.36.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-WYku83CKP3OevnYSG8Y/QO9g0rT75v1om5IvcWUwiUZJ4LanYGLVCZ8TdFG5jfsq4Ej/lu2wwDAULEUnRIMBSw==} - peerDependencies: - '@tanstack/react-query': ^4.36.1 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@tanstack/react-query-devtools@4.36.1(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/match-sorter-utils': 8.11.7 - '@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react@18.2.0) + '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) superjson: 1.13.3 use-sync-external-store: 1.2.0(react@18.2.0) - dev: true - /@tanstack/react-query@4.36.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true + '@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/query-core': 4.36.1 react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) use-sync-external-store: 1.2.0(react@18.2.0) + optionalDependencies: + react-dom: 18.2.0(react@18.2.0) - /@tanstack/react-virtual@3.0.0-beta.9(react@18.2.0): - resolution: {integrity: sha512-zW914lzyPWunqKaHYwiOu/R7k/LJ43W6lutpCAeDbYqaOOme5tp/G+/PCpnK2vKseUnCpKM7x/WicrQoDX8H0A==} - engines: {node: '>=12'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + '@tanstack/react-virtual@3.0.0-beta.9(react@18.2.0)': dependencies: '@reach/observe-rect': 1.2.0 react: 18.2.0 - dev: false - /@tanstack/virtual-core@3.0.0-beta.9: - resolution: {integrity: sha512-Aaku1DMatF8GNbAZj2ahGN9F3l6KLe2n1jzgAUO8IT07D1spoZ9jCeazOQ7gvCIIEZjceuL9klx08MnsFJdCqg==} - engines: {node: '>=12'} + '@tanstack/virtual-core@3.0.0-beta.9': dependencies: '@reach/observe-rect': 1.2.0 - dev: false - /@tauri-apps/api@1.5.3: - resolution: {integrity: sha512-zxnDjHHKjOsrIzZm6nO5Xapb/BxqUq1tc7cGkFXsFkGTsSWgCPH1D8mm0XS9weJY2OaR73I3k3S+b7eSzJDfqA==} - engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} - dev: false + '@tauri-apps/api@1.5.3': {} - /@trysound/sax@0.2.0: - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - dev: true + '@trysound/sax@0.2.0': {} - /@types/acorn@4.0.6: - resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + '@types/acorn@4.0.6': dependencies: '@types/estree': 1.0.5 - dev: false - /@types/babel__core@7.20.5: - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.23.6 '@babel/types': 7.23.6 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.5 - dev: false - /@types/babel__generator@7.6.8: - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__generator@7.6.8': dependencies: '@babel/types': 7.23.6 - dev: false - /@types/babel__template@7.4.4: - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@types/babel__template@7.4.4': dependencies: '@babel/parser': 7.23.6 '@babel/types': 7.23.6 - dev: false - /@types/babel__traverse@7.20.5: - resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} + '@types/babel__traverse@7.20.5': dependencies: '@babel/types': 7.23.6 - dev: false - /@types/byte-size@8.1.2: - resolution: {integrity: sha512-jGyVzYu6avI8yuqQCNTZd65tzI8HZrLjKX9sdMqZrGWVlNChu0rf6p368oVEDCYJe5BMx2Ov04tD1wqtgTwGSA==} - dev: true + '@types/byte-size@8.1.2': {} - /@types/cookie@0.6.0: - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - dev: false + '@types/cookie@0.6.0': {} - /@types/d3-array@3.2.1: - resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} - dev: false + '@types/d3-array@3.2.1': {} - /@types/d3-color@3.1.3: - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - dev: false + '@types/d3-color@3.1.3': {} - /@types/d3-ease@3.0.2: - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - dev: false + '@types/d3-ease@3.0.2': {} - /@types/d3-interpolate@3.0.4: - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + '@types/d3-interpolate@3.0.4': dependencies: '@types/d3-color': 3.1.3 - dev: false - /@types/d3-path@3.0.2: - resolution: {integrity: sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==} - dev: false + '@types/d3-path@3.0.2': {} - /@types/d3-scale@4.0.8: - resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} + '@types/d3-scale@4.0.8': dependencies: '@types/d3-time': 3.0.3 - dev: false - /@types/d3-shape@3.1.6: - resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} + '@types/d3-shape@3.1.6': dependencies: '@types/d3-path': 3.0.2 - dev: false - /@types/d3-time@3.0.3: - resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==} - dev: false + '@types/d3-time@3.0.3': {} - /@types/d3-timer@3.0.2: - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - dev: false + '@types/d3-timer@3.0.2': {} - /@types/debug@4.1.12: - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 - dev: false - /@types/eslint@8.56.2: - resolution: {integrity: sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==} + '@types/eslint@8.56.2': dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 - dev: true - /@types/estree-jsx@1.0.3: - resolution: {integrity: sha512-pvQ+TKeRHeiUGRhvYwRrQ/ISnohKkSJR14fT2yqyZ4e9K5vqc7hrtY2Y1Dw0ZwAzQ6DQsxsaCUuSIIi8v0Cq6w==} + '@types/estree-jsx@1.0.3': dependencies: '@types/estree': 1.0.5 - dev: false - /@types/estree@1.0.5: - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.5': {} - /@types/file-saver@2.0.7: - resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} - dev: true + '@types/file-saver@2.0.7': {} - /@types/hast@3.0.3: - resolution: {integrity: sha512-2fYGlaDy/qyLlhidX42wAH0KBi2TCjKMH8CHmBXgRlJ3Y+OXTiqsPQ6IWarZKwF1JoUcAJdPogv1d4b0COTpmQ==} + '@types/hast@3.0.3': dependencies: '@types/unist': 3.0.2 - dev: false - /@types/history@4.7.11: - resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} - dev: true + '@types/history@4.7.11': {} - /@types/json-schema@7.0.15: - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true + '@types/json-schema@7.0.15': {} - /@types/json5@0.0.29: - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - dev: true + '@types/json5@0.0.29': {} - /@types/lodash-es@4.17.12: - resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + '@types/lodash-es@4.17.12': dependencies: '@types/lodash': 4.14.202 - dev: true - /@types/lodash@4.14.202: - resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} - dev: true + '@types/lodash@4.14.202': {} - /@types/mdast@4.0.3: - resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} + '@types/mdast@4.0.3': dependencies: '@types/unist': 3.0.2 - dev: false - /@types/mdx@2.0.10: - resolution: {integrity: sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==} - dev: false + '@types/mdx@2.0.10': {} - /@types/minimist@1.2.5: - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - dev: true + '@types/minimist@1.2.5': {} - /@types/ms@0.7.34: - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - dev: false + '@types/ms@0.7.34': {} - /@types/node@18.15.13: - resolution: {integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==} - dev: false + '@types/node@18.15.13': {} - /@types/node@20.11.7: - resolution: {integrity: sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==} + '@types/node@20.11.7': dependencies: undici-types: 5.26.5 - /@types/normalize-package-data@2.4.4: - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - dev: true + '@types/normalize-package-data@2.4.4': {} - /@types/parse-json@4.0.2: - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - dev: true + '@types/parse-json@4.0.2': {} - /@types/prismjs@1.26.3: - resolution: {integrity: sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==} - dev: false + '@types/prismjs@1.26.3': {} - /@types/prop-types@15.7.11: - resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} + '@types/prop-types@15.7.11': {} - /@types/react-dom@18.2.18: - resolution: {integrity: sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==} + '@types/react-dom@18.2.18': dependencies: '@types/react': 18.2.48 - dev: true - /@types/react-router-dom@5.3.3: - resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} + '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 '@types/react': 18.2.48 '@types/react-router': 5.1.20 - dev: true - /@types/react-router@5.1.20: - resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 '@types/react': 18.2.48 - dev: true - /@types/react-virtualized-auto-sizer@1.0.4: - resolution: {integrity: sha512-nhYwlFiYa8M3S+O2T9QO/e1FQUYMr/wJENUdf/O0dhRi1RS/93rjrYQFYdbUqtdFySuhrtnEDX29P6eKOttY+A==} + '@types/react-virtualized-auto-sizer@1.0.4': dependencies: '@types/react': 18.2.48 - dev: false - /@types/react-window@1.8.8: - resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==} + '@types/react-window@1.8.8': dependencies: '@types/react': 18.2.48 - dev: false - /@types/react@18.2.48: - resolution: {integrity: sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==} + '@types/react@18.2.48': dependencies: '@types/prop-types': 15.7.11 '@types/scheduler': 0.16.8 csstype: 3.1.3 - /@types/scheduler@0.16.8: - resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==} + '@types/scheduler@0.16.8': {} - /@types/semver@7.5.6: - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} - dev: true + '@types/semver@7.5.6': {} - /@types/statuses@2.0.4: - resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==} - dev: false + '@types/statuses@2.0.4': {} - /@types/unist@2.0.10: - resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} - dev: false + '@types/unist@2.0.10': {} - /@types/unist@3.0.2: - resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} - dev: false + '@types/unist@3.0.2': {} - /@typescript-eslint/eslint-plugin@6.19.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-roQScUGFruWod9CEyoV5KlCYrubC/fvG8/1zXuT0WTcxX87GnMMmnksMwSg99lo1xiKrBzw2icsJPMAw1OtKxg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/eslint-plugin@6.19.1(@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.10.0 '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3) @@ -3063,20 +10766,12 @@ packages: natural-compare: 1.4.0 semver: 7.5.4 ts-api-utils: 1.0.3(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-WEfX22ziAh6pRE9jnbkkLGp/4RhTpffr2ZK5bJ18M8mIfA8A+k97U9ZyaXCEJRlmMHh7R9MJZWXp/r73DzINVQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3)': dependencies: '@typescript-eslint/scope-manager': 6.19.1 '@typescript-eslint/types': 6.19.1 @@ -3084,52 +10779,31 @@ packages: '@typescript-eslint/visitor-keys': 6.19.1 debug: 4.3.4 eslint: 8.56.0 + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/scope-manager@6.19.1: - resolution: {integrity: sha512-4CdXYjKf6/6aKNMSly/BP4iCSOpvMmqtDzRtqFyyAae3z5kkqEjKndR5vDHL8rSuMIIWP8u4Mw4VxLyxZW6D5w==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/scope-manager@6.19.1': dependencies: '@typescript-eslint/types': 6.19.1 '@typescript-eslint/visitor-keys': 6.19.1 - dev: true - /@typescript-eslint/type-utils@6.19.1(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-0vdyld3ecfxJuddDjACUvlAeYNrHP/pDeQk2pWBR2ESeEzQhg52DF53AbI9QCBkYE23lgkhLCZNkHn2hEXXYIg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/type-utils@6.19.1(eslint@8.56.0)(typescript@5.3.3)': dependencies: '@typescript-eslint/typescript-estree': 6.19.1(typescript@5.3.3) '@typescript-eslint/utils': 6.19.1(eslint@8.56.0)(typescript@5.3.3) debug: 4.3.4 eslint: 8.56.0 ts-api-utils: 1.0.3(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/types@6.19.1: - resolution: {integrity: sha512-6+bk6FEtBhvfYvpHsDgAL3uo4BfvnTnoge5LrrCj2eJN8g3IJdLTD4B/jK3Q6vo4Ql/Hoip9I8aB6fF+6RfDqg==} - engines: {node: ^16.0.0 || >=18.0.0} - dev: true + '@typescript-eslint/types@6.19.1': {} - /@typescript-eslint/typescript-estree@6.19.1(typescript@5.3.3): - resolution: {integrity: sha512-aFdAxuhzBFRWhy+H20nYu19+Km+gFfwNO4TEqyszkMcgBDYQjmPJ61erHxuT2ESJXhlhrO7I5EFIlZ+qGR8oVA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/typescript-estree@6.19.1(typescript@5.3.3)': dependencies: '@typescript-eslint/types': 6.19.1 '@typescript-eslint/visitor-keys': 6.19.1 @@ -3139,16 +10813,12 @@ packages: minimatch: 9.0.3 semver: 7.5.4 ts-api-utils: 1.0.3(typescript@5.3.3) + optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: - supports-color - dev: true - /@typescript-eslint/utils@6.19.1(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-JvjfEZuP5WoMqwh9SPAPDSHSg9FBHHGhjPugSRxu5jMfjvBpq5/sGTD+9M9aQ5sh6iJ8AY/Kk/oUYVEMAPwi7w==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + '@typescript-eslint/utils@6.19.1(eslint@8.56.0)(typescript@5.3.3)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@types/json-schema': 7.0.15 @@ -3161,25 +10831,15 @@ packages: transitivePeerDependencies: - supports-color - typescript - dev: true - /@typescript-eslint/visitor-keys@6.19.1: - resolution: {integrity: sha512-gkdtIO+xSO/SmI0W68DBg4u1KElmIUo3vXzgHyGPs6cxgB0sa3TlptRAAE0hUY1hM6FcDKEv7aIwiTGm76cXfQ==} - engines: {node: ^16.0.0 || >=18.0.0} + '@typescript-eslint/visitor-keys@6.19.1': dependencies: '@typescript-eslint/types': 6.19.1 eslint-visitor-keys: 3.4.3 - dev: true - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@ungap/structured-clone@1.2.0': {} - /@vitejs/plugin-legacy@5.3.0(esbuild@0.19.12)(terser@5.27.0)(vite@5.0.12): - resolution: {integrity: sha512-BhW+WcJmEgW5G/1UQRiVQ7wz9/ZPnxqzExT9n0zAk4RlqQQ/26udIeXzdU8+03AGnaF61wmZlCspexgEnxFWMA==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - terser: ^5.4.0 - vite: ^5.0.0 + '@vitejs/plugin-legacy@5.3.0(esbuild@0.19.12)(terser@5.27.0)(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0))': dependencies: '@babel/core': 7.23.9 '@babel/preset-env': 7.23.8(@babel/core@7.23.9) @@ -3194,23 +10854,15 @@ packages: transitivePeerDependencies: - esbuild - supports-color - dev: false - /@vitejs/plugin-react-swc@3.5.0(vite@5.0.12): - resolution: {integrity: sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==} - peerDependencies: - vite: ^4 || ^5 + '@vitejs/plugin-react-swc@3.5.0(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0))': dependencies: '@swc/core': 1.3.106 vite: 5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - '@swc/helpers' - /@vitejs/plugin-react@4.2.1(vite@5.0.12): - resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 + '@vitejs/plugin-react@4.2.1(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0))': dependencies: '@babel/core': 7.23.9 '@babel/plugin-transform-react-jsx-self': 7.23.3(@babel/core@7.23.9) @@ -3220,191 +10872,124 @@ packages: vite: 5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - supports-color - dev: false - /JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 through: 2.3.8 - dev: true - /accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} + accepts@1.3.8: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 - dev: false - /acorn-jsx@5.3.2(acorn@8.11.3): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-jsx@5.3.2(acorn@8.11.3): dependencies: acorn: 8.11.3 - /acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true + acorn@8.11.3: {} - /add-stream@1.0.0: - resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} - dev: true + add-stream@1.0.0: {} - /aes-js@4.0.0-beta.5: - resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} - dev: false + aes-js@4.0.0-beta.5: {} - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + 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 - dev: true - /ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + ajv@8.12.0: dependencies: fast-deep-equal: 3.1.3 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 - dev: true - /ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-align@3.0.1: dependencies: string-width: 4.2.3 - dev: false - /ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 - dev: false - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} + ansi-regex@5.0.1: {} - /ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} + ansi-regex@6.0.1: {} - /ansi-sequence-parser@1.1.1: - resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==} - dev: true + ansi-sequence-parser@1.1.1: {} - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} + ansi-styles@6.2.1: {} - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true + argparse@2.0.1: {} - /aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.0: dependencies: dequal: 2.0.3 - dev: true - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + array-buffer-byte-length@1.0.0: dependencies: call-bind: 1.0.5 is-array-buffer: 3.0.2 - dev: true - /array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - dev: true + array-ify@1.0.0: {} - /array-includes@3.1.7: - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} - engines: {node: '>= 0.4'} + array-includes@3.1.7: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 get-intrinsic: 1.2.2 is-string: 1.0.7 - dev: true - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true + array-union@2.1.0: {} - /array.prototype.findlastindex@1.2.3: - resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} - engines: {node: '>= 0.4'} + array.prototype.findlastindex@1.2.3: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 es-shim-unscopables: 1.0.2 get-intrinsic: 1.2.2 - dev: true - /array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} + array.prototype.flat@1.3.2: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 es-shim-unscopables: 1.0.2 - dev: true - /array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} + array.prototype.flatmap@1.3.2: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 es-shim-unscopables: 1.0.2 - dev: true - /array.prototype.tosorted@1.1.2: - resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} + array.prototype.tosorted@1.1.2: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 es-shim-unscopables: 1.0.2 get-intrinsic: 1.2.2 - dev: true - /arraybuffer.prototype.slice@1.0.2: - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} - engines: {node: '>= 0.4'} + arraybuffer.prototype.slice@1.0.2: dependencies: array-buffer-byte-length: 1.0.0 call-bind: 1.0.5 @@ -3413,43 +10998,22 @@ packages: get-intrinsic: 1.2.2 is-array-buffer: 3.0.2 is-shared-array-buffer: 1.0.2 - dev: true - /arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - dev: true + arrify@1.0.1: {} - /ast-types-flow@0.0.8: - resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - dev: true + ast-types-flow@0.0.8: {} - /astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - dev: true + astral-regex@2.0.0: {} - /astring@1.8.6: - resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} - hasBin: true - dev: false + astring@1.8.6: {} - /asynciterator.prototype@1.0.0: - resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} + asynciterator.prototype@1.0.0: dependencies: has-symbols: 1.0.3 - dev: true - /asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: false + asynckit@0.4.0: {} - /autoprefixer@10.4.17(postcss@8.4.33): - resolution: {integrity: sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 + autoprefixer@10.4.17(postcss@8.4.33): dependencies: browserslist: 4.22.2 caniuse-lite: 1.0.30001580 @@ -3458,52 +11022,32 @@ packages: picocolors: 1.0.0 postcss: 8.4.33 postcss-value-parser: 4.2.0 - dev: true - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - dev: true + available-typed-arrays@1.0.5: {} - /axe-core@4.7.0: - resolution: {integrity: sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==} - engines: {node: '>=4'} - dev: true + axe-core@4.7.0: {} - /axe-core@4.8.3: - resolution: {integrity: sha512-d5ZQHPSPkF9Tw+yfyDcRoUOc4g/8UloJJe5J8m4L5+c7AtDdjDLRxew/knnI4CxvtdxEUVgWz4x3OIQUIFiMfw==} - engines: {node: '>=4'} - dev: false + axe-core@4.8.3: {} - /axios@1.6.7: - resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} + axios@1.6.7: dependencies: follow-redirects: 1.15.5 form-data: 4.0.0 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - dev: false - /axobject-query@3.2.1: - resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} + axobject-query@3.2.1: dependencies: dequal: 2.0.3 - dev: true - /babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} + babel-plugin-macros@3.1.0: dependencies: '@babel/runtime': 7.23.8 cosmiconfig: 7.1.0 resolve: 1.22.8 - dev: true - /babel-plugin-polyfill-corejs2@0.4.8(@babel/core@7.23.9): - resolution: {integrity: sha512-OtIuQfafSzpo/LhnJaykc0R/MMnuLSSVjVYy9mHArIZ9qTCSZ6TpWCuEKZYVoN//t8HqBNScHrOtCrIK5IaGLg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs2@0.4.8(@babel/core@7.23.9): dependencies: '@babel/compat-data': 7.23.5 '@babel/core': 7.23.9 @@ -3511,78 +11055,47 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: false - /babel-plugin-polyfill-corejs3@0.8.7(@babel/core@7.23.9): - resolution: {integrity: sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-corejs3@0.8.7(@babel/core@7.23.9): dependencies: '@babel/core': 7.23.9 '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.23.9) core-js-compat: 3.35.1 transitivePeerDependencies: - supports-color - dev: false - /babel-plugin-polyfill-regenerator@0.5.5(@babel/core@7.23.9): - resolution: {integrity: sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + babel-plugin-polyfill-regenerator@0.5.5(@babel/core@7.23.9): dependencies: '@babel/core': 7.23.9 '@babel/helper-define-polyfill-provider': 0.5.0(@babel/core@7.23.9) transitivePeerDependencies: - supports-color - dev: false - /bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - dev: false + bail@2.0.2: {} - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true + balanced-match@1.0.2: {} - /balanced-match@2.0.0: - resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - dev: true + balanced-match@2.0.0: {} - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false + base64-js@1.5.1: {} - /bcp-47-match@2.0.3: - resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} - dev: false + bcp-47-match@2.0.3: {} - /big-integer@1.6.52: - resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} - engines: {node: '>=0.6'} - dev: false + big-integer@1.6.52: {} - /bignumber.js@9.1.2: - resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} - dev: false + bignumber.js@9.1.2: {} - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} + binary-extensions@2.2.0: {} - /bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + bl@4.1.0: dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - dev: false - /boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + boolbase@1.0.0: {} - /boxen@7.1.1: - resolution: {integrity: sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==} - engines: {node: '>=14.16'} + boxen@7.1.1: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 @@ -3592,163 +11105,100 @@ packages: type-fest: 2.19.0 widest-line: 4.0.1 wrap-ansi: 8.1.0 - dev: false - /bplist-parser@0.2.0: - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} - engines: {node: '>= 5.10.0'} + bplist-parser@0.2.0: dependencies: big-integer: 1.6.52 - dev: false - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 - dev: true - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} + braces@3.0.3: dependencies: - fill-range: 7.0.1 + fill-range: 7.1.1 - /browserslist@4.22.2: - resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + browserslist@4.22.2: dependencies: caniuse-lite: 1.0.30001580 electron-to-chromium: 1.4.645 node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.22.2) - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-from@1.1.2: {} - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: false - /bundle-name@3.0.0: - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} - engines: {node: '>=12'} + bundle-name@3.0.0: dependencies: run-applescript: 5.0.0 - dev: false - /byte-size@8.1.1: - resolution: {integrity: sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg==} - engines: {node: '>=12.17'} - dev: false + byte-size@8.1.1: {} - /cache-content-type@1.0.1: - resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} - engines: {node: '>= 6.0.0'} + cache-content-type@1.0.1: dependencies: mime-types: 2.1.35 ylru: 1.3.2 - dev: false - /call-bind@1.0.5: - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + call-bind@1.0.5: dependencies: function-bind: 1.1.2 get-intrinsic: 1.2.2 set-function-length: 1.2.0 - dev: true - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true + callsites@3.1.0: {} - /camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} + camelcase-keys@6.2.2: dependencies: camelcase: 5.3.1 map-obj: 4.3.0 quick-lru: 4.0.1 - dev: true - /camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} + camelcase@5.3.1: {} - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - dev: true + camelcase@6.3.0: {} - /camelcase@7.0.1: - resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} - engines: {node: '>=14.16'} - dev: false + camelcase@7.0.1: {} - /caniuse-lite@1.0.30001580: - resolution: {integrity: sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==} + caniuse-lite@1.0.30001580: {} - /ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - dev: false + ccount@2.0.1: {} - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + 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: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - /chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: false + chalk@5.3.0: {} - /character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - dev: false + character-entities-html4@2.1.0: {} - /character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - dev: false + character-entities-legacy@3.0.0: {} - /character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - dev: false + character-entities@2.0.2: {} - /character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - dev: false + character-reference-invalid@2.0.1: {} - /chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - dev: false + chardet@0.7.0: {} - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} + chokidar@3.5.3: dependencies: anymatch: 3.1.3 - braces: 3.0.2 + braces: 3.0.3 glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.3 @@ -3757,150 +11207,87 @@ packages: optionalDependencies: fsevents: 2.3.3 - /classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - dev: false + classnames@2.5.1: {} - /cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} - dev: false + cli-boxes@3.0.0: {} - /cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 - dev: false - /cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - dev: false + cli-spinners@2.9.2: {} - /cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - dev: false + cli-width@3.0.0: {} - /cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + cliui@6.0.0: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 6.2.0 - dev: false - /cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@7.0.4: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true - /cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - /clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - dev: false + clone@1.0.4: {} - /clsx@2.1.0: - resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} - engines: {node: '>=6'} - dev: false + clsx@2.1.0: {} - /co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - dev: false + co@4.6.0: {} - /collapse-white-space@2.1.0: - resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} - dev: false + collapse-white-space@2.1.0: {} - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@1.9.3: dependencies: color-name: 1.1.3 - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + color-convert@2.0.1: dependencies: color-name: 1.1.4 - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.3: {} - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-name@1.1.4: {} - /colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - dev: true + colord@2.9.3: {} - /combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 - dev: false - /comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - dev: false + comma-separated-tokens@2.0.3: {} - /commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} - dev: false + commander@11.1.0: {} - /commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@2.20.3: {} - /commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - dev: true + commander@7.2.0: {} - /commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - dev: true + commander@9.5.0: {} - /compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + compare-func@2.0.0: dependencies: array-ify: 1.0.0 dot-prop: 5.3.0 - dev: true - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true + concat-map@0.0.1: {} - /concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - engines: {'0': node >= 6.0} + concat-stream@2.0.0: dependencies: buffer-from: 1.1.2 inherits: 2.0.4 readable-stream: 3.6.2 typedarray: 0.0.6 - dev: true - /concurrently@8.2.2: - resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} - engines: {node: ^14.13.0 || >=16.0.0} - hasBin: true + concurrently@8.2.2: dependencies: chalk: 4.1.2 date-fns: 2.30.0 @@ -3911,58 +11298,35 @@ packages: supports-color: 8.1.1 tree-kill: 1.2.2 yargs: 17.7.2 - dev: true - /content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 - dev: false - /content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - dev: false + content-type@1.0.5: {} - /conventional-changelog-angular@5.0.13: - resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} - engines: {node: '>=10'} + conventional-changelog-angular@5.0.13: dependencies: compare-func: 2.0.0 q: 1.5.1 - dev: true - /conventional-changelog-atom@2.0.8: - resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} - engines: {node: '>=10'} + conventional-changelog-atom@2.0.8: dependencies: q: 1.5.1 - dev: true - /conventional-changelog-codemirror@2.0.8: - resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} - engines: {node: '>=10'} + conventional-changelog-codemirror@2.0.8: dependencies: q: 1.5.1 - dev: true - /conventional-changelog-config-spec@2.1.0: - resolution: {integrity: sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==} - dev: true + conventional-changelog-config-spec@2.1.0: {} - /conventional-changelog-conventionalcommits@4.6.3: - resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} - engines: {node: '>=10'} + conventional-changelog-conventionalcommits@4.6.3: dependencies: compare-func: 2.0.0 lodash: 4.17.21 q: 1.5.1 - dev: true - /conventional-changelog-core@4.2.4: - resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} - engines: {node: '>=10'} + conventional-changelog-core@4.2.4: dependencies: add-stream: 1.0.0 conventional-changelog-writer: 5.0.1 @@ -3978,53 +11342,31 @@ packages: read-pkg: 3.0.0 read-pkg-up: 3.0.0 through2: 4.0.2 - dev: true - /conventional-changelog-ember@2.0.9: - resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} - engines: {node: '>=10'} + conventional-changelog-ember@2.0.9: dependencies: q: 1.5.1 - dev: true - /conventional-changelog-eslint@3.0.9: - resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} - engines: {node: '>=10'} + conventional-changelog-eslint@3.0.9: dependencies: q: 1.5.1 - dev: true - /conventional-changelog-express@2.0.6: - resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} - engines: {node: '>=10'} + conventional-changelog-express@2.0.6: dependencies: q: 1.5.1 - dev: true - /conventional-changelog-jquery@3.0.11: - resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} - engines: {node: '>=10'} + conventional-changelog-jquery@3.0.11: dependencies: q: 1.5.1 - dev: true - /conventional-changelog-jshint@2.0.9: - resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} - engines: {node: '>=10'} + conventional-changelog-jshint@2.0.9: dependencies: compare-func: 2.0.0 q: 1.5.1 - dev: true - /conventional-changelog-preset-loader@2.3.4: - resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} - engines: {node: '>=10'} - dev: true + conventional-changelog-preset-loader@2.3.4: {} - /conventional-changelog-writer@5.0.1: - resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} - engines: {node: '>=10'} - hasBin: true + conventional-changelog-writer@5.0.1: dependencies: conventional-commits-filter: 2.0.7 dateformat: 3.0.3 @@ -4035,11 +11377,8 @@ packages: semver: 6.3.1 split: 1.0.1 through2: 4.0.2 - dev: true - /conventional-changelog@3.1.25: - resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} - engines: {node: '>=10'} + conventional-changelog@3.1.25: dependencies: conventional-changelog-angular: 5.0.13 conventional-changelog-atom: 2.0.8 @@ -4052,20 +11391,13 @@ packages: conventional-changelog-jquery: 3.0.11 conventional-changelog-jshint: 2.0.9 conventional-changelog-preset-loader: 2.3.4 - dev: true - /conventional-commits-filter@2.0.7: - resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} - engines: {node: '>=10'} + conventional-commits-filter@2.0.7: dependencies: lodash.ismatch: 4.4.0 modify-values: 1.0.1 - dev: true - /conventional-commits-parser@3.2.4: - resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} - engines: {node: '>=10'} - hasBin: true + conventional-commits-parser@3.2.4: dependencies: JSONStream: 1.3.5 is-text-path: 1.0.1 @@ -4073,12 +11405,8 @@ packages: meow: 8.1.2 split2: 3.2.2 through2: 4.0.2 - dev: true - /conventional-recommended-bump@6.1.0: - resolution: {integrity: sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==} - engines: {node: '>=10'} - hasBin: true + conventional-recommended-bump@6.1.0: dependencies: concat-stream: 2.0.0 conventional-changelog-preset-loader: 2.3.4 @@ -4088,538 +11416,302 @@ packages: git-semver-tags: 4.1.1 meow: 8.1.2 q: 1.5.1 - dev: true - /convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: true + convert-source-map@1.9.0: {} - /convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + convert-source-map@2.0.0: {} - /cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} - dev: false + cookie@0.5.0: {} - /cookies@0.9.1: - resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} - engines: {node: '>= 0.8'} + cookies@0.9.1: dependencies: depd: 2.0.0 keygrip: 1.1.0 - dev: false - /copy-anything@3.0.5: - resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} - engines: {node: '>=12.13'} + copy-anything@3.0.5: dependencies: is-what: 4.1.16 - dev: true - /core-js-compat@3.35.1: - resolution: {integrity: sha512-sftHa5qUJY3rs9Zht1WEnmkvXputCyDBczPnr7QDgL8n3qrF3CMXY4VPSYtOLLiOUJcah2WNXREd48iOl6mQIw==} + core-js-compat@3.35.1: dependencies: browserslist: 4.22.2 - dev: false - /core-js@3.35.1: - resolution: {integrity: sha512-IgdsbxNyMskrTFxa9lWHyMwAJU5gXOPP+1yO+K59d50VLVAIDAbs7gIv705KzALModfK3ZrSZTPNpC0PQgIZuw==} - requiresBuild: true - dev: false + core-js@3.35.1: {} - /core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: true + core-util-is@1.0.3: {} - /cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 import-fresh: 3.3.0 parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 - dev: true - /cosmiconfig@8.3.6(typescript@5.3.3): - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true + cosmiconfig@8.3.6(typescript@5.3.3): dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 + optionalDependencies: typescript: 5.3.3 - dev: true - /cosmiconfig@9.0.0(typescript@5.3.3): - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true + cosmiconfig@9.0.0(typescript@5.3.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 + optionalDependencies: typescript: 5.3.3 - dev: true - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - /css-functions-list@3.2.1: - resolution: {integrity: sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==} - engines: {node: '>=12 || >=16'} - dev: true + css-functions-list@3.2.1: {} - /css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-select@5.1.0: dependencies: boolbase: 1.0.0 css-what: 6.1.0 domhandler: 5.0.3 domutils: 3.1.0 nth-check: 2.1.1 - dev: true - /css-selector-parser@3.0.4: - resolution: {integrity: sha512-pnmS1dbKsz6KA4EW4BznyPL2xxkNDRg62hcD0v8g6DEw2W7hxOln5M953jsp9hmw5Dg57S6o/A8GOn37mbAgcQ==} - dev: false + css-selector-parser@3.0.4: {} - /css-tree@2.2.1: - resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + css-tree@2.2.1: dependencies: mdn-data: 2.0.28 source-map-js: 1.0.2 - dev: true - /css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-tree@2.3.1: dependencies: mdn-data: 2.0.30 source-map-js: 1.0.2 - dev: true - /css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - dev: true + css-what@6.1.0: {} - /cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - dev: true + cssesc@3.0.0: {} - /csso@5.0.5: - resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + csso@5.0.5: dependencies: css-tree: 2.2.1 - dev: true - /csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.1.3: {} - /d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} + d3-array@3.2.4: dependencies: internmap: 2.0.3 - dev: false - /d3-color@3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} - engines: {node: '>=12'} - dev: false + d3-color@3.1.0: {} - /d3-ease@3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} - engines: {node: '>=12'} - dev: false + d3-ease@3.0.1: {} - /d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} - engines: {node: '>=12'} - dev: false + d3-format@3.1.0: {} - /d3-interpolate@3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} - engines: {node: '>=12'} + d3-interpolate@3.0.1: dependencies: d3-color: 3.1.0 - dev: false - /d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - dev: false + d3-path@3.1.0: {} - /d3-scale@4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} - engines: {node: '>=12'} + d3-scale@4.0.2: dependencies: d3-array: 3.2.4 d3-format: 3.1.0 d3-interpolate: 3.0.1 d3-time: 3.1.0 d3-time-format: 4.1.0 - dev: false - /d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} + d3-shape@3.2.0: dependencies: d3-path: 3.1.0 - dev: false - /d3-time-format@4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} - engines: {node: '>=12'} + d3-time-format@4.1.0: dependencies: d3-time: 3.1.0 - dev: false - /d3-time@3.1.0: - resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} - engines: {node: '>=12'} + d3-time@3.1.0: dependencies: d3-array: 3.2.4 - dev: false - /d3-timer@3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} - engines: {node: '>=12'} - dev: false + d3-timer@3.0.1: {} - /damerau-levenshtein@1.0.8: - resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - dev: true + damerau-levenshtein@1.0.8: {} - /dargs@7.0.0: - resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} - engines: {node: '>=8'} - dev: true + dargs@7.0.0: {} - /dashify@2.0.0: - resolution: {integrity: sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A==} - engines: {node: '>=4'} - dev: true + dashify@2.0.0: {} - /date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} + date-fns@2.30.0: dependencies: '@babel/runtime': 7.23.8 - dev: true - /dateformat@3.0.3: - resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} - dev: true + dateformat@3.0.3: {} - /dayjs@1.11.10: - resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} - dev: false + dayjs@1.11.10: {} - /debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@3.2.7: dependencies: ms: 2.1.3 - dev: true - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + debug@4.3.4: dependencies: ms: 2.1.2 - /decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} + decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 map-obj: 1.0.1 - dev: true - /decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} + decamelize@1.2.0: {} - /decimal.js-light@2.5.1: - resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - dev: false + decimal.js-light@2.5.1: {} - /decode-named-character-reference@1.0.2: - resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + decode-named-character-reference@1.0.2: dependencies: character-entities: 2.0.2 - dev: false - /decode-uri-component@0.4.1: - resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==} - engines: {node: '>=14.16'} - dev: false + decode-uri-component@0.4.1: {} - /deep-equal@1.0.1: - resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} - dev: false + deep-equal@1.0.1: {} - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true + deep-is@0.1.4: {} - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - dev: true + deepmerge@4.3.1: {} - /default-browser-id@3.0.0: - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} - engines: {node: '>=12'} + default-browser-id@3.0.0: dependencies: bplist-parser: 0.2.0 untildify: 4.0.0 - dev: false - /default-browser@4.0.0: - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} - engines: {node: '>=14.16'} + default-browser@4.0.0: dependencies: bundle-name: 3.0.0 default-browser-id: 3.0.0 execa: 7.2.0 titleize: 3.0.0 - dev: false - /defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + defaults@1.0.4: dependencies: clone: 1.0.4 - dev: false - /define-data-property@1.1.1: - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} - engines: {node: '>= 0.4'} + define-data-property@1.1.1: dependencies: get-intrinsic: 1.2.2 gopd: 1.0.1 has-property-descriptors: 1.0.1 - dev: true - /define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - dev: false + define-lazy-prop@3.0.0: {} - /define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} + define-properties@1.2.1: dependencies: define-data-property: 1.1.1 has-property-descriptors: 1.0.1 object-keys: 1.1.1 - dev: true - /delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - dev: false + delayed-stream@1.0.0: {} - /delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dev: false + delegates@1.0.0: {} - /depd@1.1.2: - resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} - engines: {node: '>= 0.6'} - dev: false + depd@1.1.2: {} - /depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - dev: false + depd@2.0.0: {} - /dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} + dequal@2.0.3: {} - /destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - dev: false + destroy@1.2.0: {} - /detect-browser@5.3.0: - resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} - dev: false + detect-browser@5.3.0: {} - /detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - dev: true + detect-indent@6.1.0: {} - /detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - dev: true + detect-newline@3.1.0: {} - /devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + devlop@1.1.0: dependencies: dequal: 2.0.3 - dev: false - /dijkstrajs@1.0.3: - resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} - dev: false + dijkstrajs@1.0.3: {} - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 - dev: true - /direction@2.0.1: - resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} - hasBin: true - dev: false + direction@2.0.1: {} - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} + doctrine@2.1.0: dependencies: esutils: 2.0.3 - dev: true - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} + doctrine@3.0.0: dependencies: esutils: 2.0.3 - dev: true - /dom-helpers@3.4.0: - resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==} + dom-helpers@3.4.0: dependencies: '@babel/runtime': 7.23.8 - dev: false - /dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 entities: 4.5.0 - /domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + domelementtype@2.3.0: {} - /domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} + domhandler@5.0.3: dependencies: domelementtype: 2.3.0 - /domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + domutils@3.1.0: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 domhandler: 5.0.3 - /dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dot-case@3.0.4: dependencies: no-case: 3.0.4 tslib: 2.6.2 - dev: true - /dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 - dev: true - /dotgitignore@2.1.0: - resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} - engines: {node: '>=6'} + dotgitignore@2.1.0: dependencies: find-up: 3.0.0 minimatch: 3.1.2 - dev: true - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + eastasianwidth@0.2.0: {} - /ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - dev: false + ee-first@1.1.1: {} - /electron-to-chromium@1.4.645: - resolution: {integrity: sha512-EeS1oQDCmnYsRDRy2zTeC336a/4LZ6WKqvSaM1jLocEk5ZuyszkQtCpsqvuvaIXGOUjwtvF6LTcS8WueibXvSw==} + electron-to-chromium@1.4.645: {} - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@8.0.0: {} - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + emoji-regex@9.2.2: {} - /encode-utf8@1.0.3: - resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==} - dev: false + encode-utf8@1.0.3: {} - /encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - dev: false + encodeurl@1.0.2: {} - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} + entities@4.5.0: {} - /env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - dev: true + env-paths@2.2.1: {} - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 - dev: true - /es-abstract@1.22.3: - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} - engines: {node: '>= 0.4'} + es-abstract@1.22.3: dependencies: array-buffer-byte-length: 1.0.0 arraybuffer.prototype.slice: 1.0.2 @@ -4660,10 +11752,8 @@ packages: typed-array-length: 1.0.4 unbox-primitive: 1.0.2 which-typed-array: 1.1.13 - dev: true - /es-iterator-helpers@1.0.15: - resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} + es-iterator-helpers@1.0.15: dependencies: asynciterator.prototype: 1.0.0 call-bind: 1.0.5 @@ -4679,38 +11769,24 @@ packages: internal-slot: 1.0.6 iterator.prototype: 1.1.2 safe-array-concat: 1.1.0 - dev: true - /es-set-tostringtag@2.0.2: - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} - engines: {node: '>= 0.4'} + es-set-tostringtag@2.0.2: dependencies: get-intrinsic: 1.2.2 has-tostringtag: 1.0.0 hasown: 2.0.0 - dev: true - /es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + es-shim-unscopables@1.0.2: dependencies: hasown: 2.0.0 - dev: true - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} + es-to-primitive@1.2.1: dependencies: is-callable: 1.2.7 is-date-object: 1.0.5 is-symbol: 1.0.4 - dev: true - /esbuild-plugin-browserslist@0.10.0(browserslist@4.22.2)(esbuild@0.19.12): - resolution: {integrity: sha512-rZWFcp3l+73xDiJB+Vl9UqP1VVs+L4E0lygbwJl6UTmW2qQago7DLT56hBu0vocH/TtZsAcRHj0+qHqkkB5Gww==} - engines: {node: '>=18'} - peerDependencies: - browserslist: ^4.21.8 - esbuild: ~0.19.2 + esbuild-plugin-browserslist@0.10.0(browserslist@4.22.2)(esbuild@0.19.12): dependencies: browserslist: 4.22.2 debug: 4.3.4 @@ -4718,13 +11794,8 @@ packages: zod: 3.22.4 transitivePeerDependencies: - supports-color - dev: false - /esbuild@0.19.12: - resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true + esbuild@0.19.12: optionalDependencies: '@esbuild/aix-ppc64': 0.19.12 '@esbuild/android-arm': 0.19.12 @@ -4750,87 +11821,40 @@ packages: '@esbuild/win32-ia32': 0.19.12 '@esbuild/win32-x64': 0.19.12 - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} + escalade@3.1.1: {} - /escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - dev: false + escape-html@1.0.3: {} - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + escape-string-regexp@1.0.5: {} - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true + escape-string-regexp@4.0.0: {} - /escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - dev: false + escape-string-regexp@5.0.0: {} - /eslint-config-prettier@9.1.0(eslint@8.56.0): - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' + eslint-config-prettier@9.1.0(eslint@8.56.0): dependencies: eslint: 8.56.0 - dev: true - /eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + 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 - dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint@8.56.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-module-utils@2.8.0(@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@8.56.0): dependencies: - '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3) debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3) eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - dev: true - /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.19.1)(eslint@8.56.0): - 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-import@2.29.1(@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0): dependencies: - '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3) array-includes: 3.1.7 array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 @@ -4839,7 +11863,7 @@ packages: doctrine: 2.1.0 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@8.56.0) hasown: 2.0.0 is-core-module: 2.13.1 is-glob: 4.0.3 @@ -4849,17 +11873,14 @@ packages: object.values: 1.1.7 semver: 6.3.1 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - dev: true - /eslint-plugin-jsx-a11y@6.8.0(eslint@8.56.0): - resolution: {integrity: sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + eslint-plugin-jsx-a11y@6.8.0(eslint@8.56.0): dependencies: '@babel/runtime': 7.23.8 aria-query: 5.3.0 @@ -4878,51 +11899,26 @@ packages: minimatch: 3.1.2 object.entries: 1.1.7 object.fromentries: 2.0.7 - dev: true - /eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.56.0)(prettier@3.2.4): - resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '*' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true + eslint-plugin-prettier@5.1.3(@types/eslint@8.56.2)(eslint-config-prettier@9.1.0(eslint@8.56.0))(eslint@8.56.0)(prettier@3.2.4): dependencies: eslint: 8.56.0 - eslint-config-prettier: 9.1.0(eslint@8.56.0) prettier: 3.2.4 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 - dev: true + optionalDependencies: + '@types/eslint': 8.56.2 + eslint-config-prettier: 9.1.0(eslint@8.56.0) - /eslint-plugin-react-hooks@4.6.0(eslint@8.56.0): - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} - 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-hooks@4.6.0(eslint@8.56.0): dependencies: eslint: 8.56.0 - dev: true - /eslint-plugin-react-refresh@0.4.5(eslint@8.56.0): - resolution: {integrity: sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==} - peerDependencies: - eslint: '>=7' + eslint-plugin-react-refresh@0.4.5(eslint@8.56.0): dependencies: eslint: 8.56.0 - dev: true - /eslint-plugin-react@7.33.2(eslint@8.56.0): - resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + eslint-plugin-react@7.33.2(eslint@8.56.0): dependencies: array-includes: 3.1.7 array.prototype.flatmap: 1.3.2 @@ -4941,33 +11937,19 @@ packages: resolve: 2.0.0-next.5 semver: 6.3.1 string.prototype.matchall: 4.0.10 - dev: true - /eslint-plugin-simple-import-sort@10.0.0(eslint@8.56.0): - resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==} - peerDependencies: - eslint: '>=5.0.0' + eslint-plugin-simple-import-sort@10.0.0(eslint@8.56.0): dependencies: eslint: 8.56.0 - dev: true - /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-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - dev: true - /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} - dev: true + eslint-visitor-keys@3.4.3: {} - /eslint@8.56.0: - resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true + eslint@8.56.0: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0) '@eslint-community/regexpp': 4.10.0 @@ -5009,87 +11991,56 @@ packages: text-table: 0.2.0 transitivePeerDependencies: - supports-color - dev: true - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + espree@9.6.1: dependencies: acorn: 8.11.3 acorn-jsx: 5.3.2(acorn@8.11.3) eslint-visitor-keys: 3.4.3 - dev: true - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} + esquery@1.5.0: dependencies: estraverse: 5.3.0 - dev: true - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 - dev: true - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true + estraverse@5.3.0: {} - /estree-util-attach-comments@3.0.0: - resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + estree-util-attach-comments@3.0.0: dependencies: '@types/estree': 1.0.5 - dev: false - /estree-util-build-jsx@3.0.1: - resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + estree-util-build-jsx@3.0.1: dependencies: '@types/estree-jsx': 1.0.3 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 estree-walker: 3.0.3 - dev: false - /estree-util-is-identifier-name@3.0.0: - resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - dev: false + estree-util-is-identifier-name@3.0.0: {} - /estree-util-to-js@2.0.0: - resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + estree-util-to-js@2.0.0: dependencies: '@types/estree-jsx': 1.0.3 astring: 1.8.6 source-map: 0.7.4 - dev: false - /estree-util-visit@2.0.0: - resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + estree-util-visit@2.0.0: dependencies: '@types/estree-jsx': 1.0.3 '@types/unist': 3.0.2 - dev: false - /estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true + estree-walker@2.0.2: {} - /estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.5 - dev: false - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + esutils@2.0.3: {} - /ethers@6.10.0: - resolution: {integrity: sha512-nMNwYHzs6V1FR3Y4cdfxSQmNgZsRj1RiTU25JwvnJLmyzw9z3SKxNc2XKDuiXXo/v9ds5Mp9m6HBabgYQQ26tA==} - engines: {node: '>=14.0.0'} + ethers@6.10.0: dependencies: '@adraffy/ens-normalize': 1.10.0 '@noble/curves': 1.2.0 @@ -5101,20 +12052,12 @@ packages: transitivePeerDependencies: - bufferutil - utf-8-validate - dev: false - /eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: false + eventemitter3@4.0.7: {} - /events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - dev: false + events@3.3.0: {} - /execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + execa@5.1.1: dependencies: cross-spawn: 7.0.3 get-stream: 6.0.1 @@ -5125,11 +12068,8 @@ packages: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 - dev: false - /execa@7.2.0: - resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} + execa@7.2.0: dependencies: cross-spawn: 7.0.3 get-stream: 6.0.1 @@ -5140,36 +12080,22 @@ packages: onetime: 6.0.0 signal-exit: 3.0.7 strip-final-newline: 3.0.0 - dev: false - /extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - dev: false + extend@3.0.2: {} - /external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} + external-editor@3.1.0: dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 - dev: false - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-deep-equal@3.1.3: {} - /fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - dev: true + fast-diff@1.3.0: {} - /fast-equals@5.0.1: - resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} - engines: {node: '>=6.0.0'} - dev: false + fast-equals@5.0.1: {} - /fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -5177,314 +12103,183 @@ packages: merge2: 1.4.1 micromatch: 4.0.5 - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true + fast-json-stable-stringify@2.1.0: {} - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true + fast-levenshtein@2.0.6: {} - /fastest-levenshtein@1.0.16: - resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} - engines: {node: '>= 4.9.1'} - dev: true + fastest-levenshtein@1.0.16: {} - /fastq@1.16.0: - resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==} + fastq@1.16.0: dependencies: reusify: 1.0.4 - /figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 - dev: true - /file-entry-cache@8.0.0: - resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} - engines: {node: '>=16.0.0'} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.0 - dev: true - /file-saver@2.0.5: - resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} - dev: false + file-saver@2.0.5: {} - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 - /filter-obj@5.1.0: - resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==} - engines: {node: '>=14.16'} - dev: false + filter-obj@5.1.0: {} - /find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - dev: true + find-root@1.1.0: {} - /find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} + find-up@2.1.0: dependencies: locate-path: 2.0.0 - dev: true - /find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} + find-up@3.0.0: dependencies: locate-path: 3.0.0 - dev: true - /find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + find-up@4.1.0: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + find-up@5.0.0: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - dev: true - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@3.2.0: dependencies: flatted: 3.2.9 keyv: 4.5.4 rimraf: 3.0.2 - dev: true - /flat-cache@4.0.0: - resolution: {integrity: sha512-EryKbCE/wxpxKniQlyas6PY1I9vwtF3uCBweX+N8KYTCn3Y12RTGtQAJ/bd5pl7kxUAc8v/R3Ake/N17OZiFqA==} - engines: {node: '>=16'} + flat-cache@4.0.0: dependencies: flatted: 3.2.9 keyv: 4.5.4 - rimraf: 5.0.5 - dev: true - - /flatted@3.2.9: - resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} - dev: true - - /follow-redirects@1.15.5: - resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - dev: false + rimraf: 5.0.5 + + flatted@3.2.9: {} - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + follow-redirects@1.15.5: {} + + for-each@0.3.3: dependencies: is-callable: 1.2.7 - dev: true - /foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} + foreground-child@3.1.1: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true - /form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} + form-data@4.0.0: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: false - /fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - dev: true + fraction.js@4.3.7: {} - /framer-motion@11.0.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-6x2poQpIWBdbZwLd73w6cKZ1I9IEPIU94C6/Swp1Zt3LJ+sB5bPe1E2wC6EH5hSISXNkMJ4afH7AdwS7MrtkWw==} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true + framer-motion@11.0.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) tslib: 2.6.2 optionalDependencies: '@emotion/is-prop-valid': 0.8.8 - dev: false + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) - /fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - dev: false + fresh@0.5.2: {} - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true + fs.realpath@1.0.0: {} - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true + fsevents@2.3.3: optional: true - /function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function-bind@1.1.2: {} - /function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} + function.prototype.name@1.1.6: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 functions-have-names: 1.2.3 - dev: true - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - dev: true + functions-have-names@1.2.3: {} - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + gensync@1.0.0-beta.2: {} - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} + get-caller-file@2.0.5: {} - /get-intrinsic@1.2.2: - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + get-intrinsic@1.2.2: dependencies: function-bind: 1.1.2 has-proto: 1.0.1 has-symbols: 1.0.3 hasown: 2.0.0 - dev: true - /get-pkg-repo@4.2.1: - resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} - engines: {node: '>=6.9.0'} - hasBin: true + get-pkg-repo@4.2.1: dependencies: '@hutson/parse-repository-url': 3.0.2 hosted-git-info: 4.1.0 through2: 2.0.5 yargs: 16.2.0 - dev: true - /get-port@7.0.0: - resolution: {integrity: sha512-mDHFgApoQd+azgMdwylJrv2DX47ywGq1i5VFJE7fZ0dttNq3iQMfsU4IvEgBHojA3KqEudyu7Vq+oN8kNaNkWw==} - engines: {node: '>=16'} - dev: false + get-port@7.0.0: {} - /get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - dev: false + get-stream@6.0.1: {} - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} + get-symbol-description@1.0.0: dependencies: call-bind: 1.0.5 get-intrinsic: 1.2.2 - dev: true - /get-text-width@1.0.3: - resolution: {integrity: sha512-kv1MaexPcR/qaZ4kN8sUDjG5pRp5ptHvxcDGDBTeGld1cmo7MnlCMH22jevyvs/VV7Ran203o7qAOq2+kWw9cA==} - dev: false + get-text-width@1.0.3: {} - /git-raw-commits@2.0.11: - resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} - engines: {node: '>=10'} - hasBin: true + git-raw-commits@2.0.11: dependencies: dargs: 7.0.0 lodash: 4.17.21 meow: 8.1.2 split2: 3.2.2 through2: 4.0.2 - dev: true - /git-remote-origin-url@2.0.0: - resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} - engines: {node: '>=4'} + git-remote-origin-url@2.0.0: dependencies: gitconfiglocal: 1.0.0 pify: 2.3.0 - dev: true - /git-semver-tags@4.1.1: - resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} - engines: {node: '>=10'} - hasBin: true + git-semver-tags@4.1.1: dependencies: meow: 8.1.2 semver: 6.3.1 - dev: true - /gitconfiglocal@1.0.0: - resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} + gitconfiglocal@1.0.0: dependencies: ini: 1.3.8 - dev: true - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - dev: true - /glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true + glob@10.3.10: dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 minimatch: 9.0.3 minipass: 7.0.4 path-scurry: 1.10.1 - dev: true - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + glob@7.2.3: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -5492,56 +12287,36 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true - /glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} + glob@8.1.0: dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 minimatch: 5.1.6 once: 1.4.0 - dev: true - /global-modules@2.0.0: - resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} - engines: {node: '>=6'} + global-modules@2.0.0: dependencies: global-prefix: 3.0.0 - dev: true - /global-prefix@3.0.0: - resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} - engines: {node: '>=6'} + global-prefix@3.0.0: dependencies: ini: 1.3.8 kind-of: 6.0.3 which: 1.3.1 - dev: true - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} + globals@11.12.0: {} - /globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} + globals@13.24.0: dependencies: type-fest: 0.20.2 - dev: true - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} + globalthis@1.0.3: dependencies: define-properties: 1.2.1 - dev: true - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -5549,11 +12324,8 @@ packages: ignore: 5.3.0 merge2: 1.4.1 slash: 3.0.0 - dev: true - /globby@14.0.0: - resolution: {integrity: sha512-/1WM/LNHRAOH9lZta77uGbq0dAEQM+XjNesWwhlERDVenqothRbnzTrL3/LrIoEPPjeUHC3vrS6TwoyxeHs7MQ==} - engines: {node: '>=18'} + globby@14.0.0: dependencies: '@sindresorhus/merge-streams': 1.0.0 fast-glob: 3.3.2 @@ -5561,39 +12333,22 @@ packages: path-type: 5.0.0 slash: 5.1.0 unicorn-magic: 0.1.0 - dev: false - /globjoin@0.1.4: - resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - dev: true + globjoin@0.1.4: {} - /globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - dev: false + globrex@0.1.2: {} - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.0.1: dependencies: get-intrinsic: 1.2.2 - dev: true - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: true + graceful-fs@4.2.11: {} - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true + graphemer@1.4.0: {} - /graphql@16.8.1: - resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - dev: false + graphql@16.8.1: {} - /handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true + handlebars@4.7.8: dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -5601,61 +12356,37 @@ packages: wordwrap: 1.0.0 optionalDependencies: uglify-js: 3.17.4 - dev: true - /hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - dev: true + hard-rejection@2.1.0: {} - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true + has-bigints@1.0.2: {} - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + has-flag@3.0.0: {} - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + has-flag@4.0.0: {} - /has-property-descriptors@1.0.1: - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + has-property-descriptors@1.0.1: dependencies: get-intrinsic: 1.2.2 - dev: true - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} - dev: true + has-proto@1.0.1: {} - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} + has-symbols@1.0.3: {} - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} + has-tostringtag@1.0.0: dependencies: has-symbols: 1.0.3 - /hasown@2.0.0: - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} - engines: {node: '>= 0.4'} + hasown@2.0.0: dependencies: function-bind: 1.1.2 - /hast-util-classnames@3.0.0: - resolution: {integrity: sha512-tI3JjoGDEBVorMAWK4jNRsfLMYmih1BUOG3VV36pH36njs1IEl7xkNrVTD2mD2yYHmQCa5R/fj61a8IAF4bRaQ==} + hast-util-classnames@3.0.0: dependencies: '@types/hast': 3.0.3 space-separated-tokens: 2.0.2 - dev: false - /hast-util-from-parse5@8.0.1: - resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} + hast-util-from-parse5@8.0.1: dependencies: '@types/hast': 3.0.3 '@types/unist': 3.0.2 @@ -5665,22 +12396,16 @@ packages: vfile: 6.0.1 vfile-location: 5.0.2 web-namespaces: 2.0.1 - dev: false - /hast-util-has-property@3.0.0: - resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + hast-util-has-property@3.0.0: dependencies: '@types/hast': 3.0.3 - dev: false - /hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + hast-util-parse-selector@4.0.0: dependencies: '@types/hast': 3.0.3 - dev: false - /hast-util-raw@9.0.2: - resolution: {integrity: sha512-PldBy71wO9Uq1kyaMch9AHIghtQvIwxBUkv823pKmkTM3oV1JxtsTNYdevMxvUHqcnOAuO65JKU2+0NOxc2ksA==} + hast-util-raw@9.0.2: dependencies: '@types/hast': 3.0.3 '@types/unist': 3.0.2 @@ -5695,10 +12420,8 @@ packages: vfile: 6.0.1 web-namespaces: 2.0.1 zwitch: 2.0.4 - dev: false - /hast-util-select@6.0.2: - resolution: {integrity: sha512-hT/SD/d/Meu+iobvgkffo1QecV8WeKWxwsNMzcTJsKw1cKTQKSR/7ArJeURLNJF9HDjp9nVoORyNNJxrvBye8Q==} + hast-util-select@6.0.2: dependencies: '@types/hast': 3.0.3 '@types/unist': 3.0.2 @@ -5716,10 +12439,8 @@ packages: space-separated-tokens: 2.0.2 unist-util-visit: 5.0.0 zwitch: 2.0.4 - dev: false - /hast-util-to-estree@3.1.0: - resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==} + hast-util-to-estree@3.1.0: dependencies: '@types/estree': 1.0.5 '@types/estree-jsx': 1.0.3 @@ -5739,10 +12460,8 @@ packages: zwitch: 2.0.4 transitivePeerDependencies: - supports-color - dev: false - /hast-util-to-jsx-runtime@2.3.0: - resolution: {integrity: sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==} + hast-util-to-jsx-runtime@2.3.0: dependencies: '@types/estree': 1.0.5 '@types/hast': 3.0.3 @@ -5761,10 +12480,8 @@ packages: vfile-message: 4.0.2 transitivePeerDependencies: - supports-color - dev: false - /hast-util-to-parse5@8.0.0: - resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + hast-util-to-parse5@8.0.0: dependencies: '@types/hast': 3.0.3 comma-separated-tokens: 2.0.3 @@ -5773,193 +12490,117 @@ packages: space-separated-tokens: 2.0.2 web-namespaces: 2.0.1 zwitch: 2.0.4 - dev: false - /hast-util-to-string@3.0.0: - resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==} + hast-util-to-string@3.0.0: dependencies: '@types/hast': 3.0.3 - dev: false - /hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.3 - dev: false - /hastscript@8.0.0: - resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + hastscript@8.0.0: dependencies: '@types/hast': 3.0.3 comma-separated-tokens: 2.0.3 hast-util-parse-selector: 4.0.0 property-information: 6.4.1 space-separated-tokens: 2.0.2 - dev: false - /headers-polyfill@4.0.2: - resolution: {integrity: sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==} - dev: false + headers-polyfill@4.0.2: {} - /hex-rgb@5.0.0: - resolution: {integrity: sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w==} - engines: {node: '>=12'} - dev: false + hex-rgb@5.0.0: {} - /history@5.3.0: - resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} + history@5.3.0: dependencies: '@babel/runtime': 7.23.8 - dev: false - /hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 - dev: true - /hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - dev: true + hosted-git-info@2.8.9: {} - /hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} + hosted-git-info@4.1.0: dependencies: lru-cache: 6.0.0 - dev: true - /html-dom-parser@5.0.7: - resolution: {integrity: sha512-2YD2/yB0QgrlkBIn0CsGaRXC89E1gtuPVpiOGC52NTzPCC83n0WMdGD+5q7lpcKqbCpnWValQbovuy/NI/0kag==} + html-dom-parser@5.0.7: dependencies: domhandler: 5.0.3 htmlparser2: 9.1.0 - dev: false - /html-react-parser@5.1.1(react@18.2.0): - resolution: {integrity: sha512-L5VK0rKN3VM7uzRH+4wxAL9elvHuCNDjyWKKjcCDR+YWW5Qr7WWSK7+e627DcePVAFi5IMqc+rAU8j/1DpC/Tw==} - peerDependencies: - react: 0.14 || 15 || 16 || 17 || 18 + html-react-parser@5.1.1(react@18.2.0): dependencies: domhandler: 5.0.3 html-dom-parser: 5.0.7 react: 18.2.0 react-property: 2.0.2 style-to-js: 1.1.10 - dev: false - /html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} - engines: {node: '>=8'} - dev: true + html-tags@3.3.1: {} - /html-url-attributes@3.0.0: - resolution: {integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==} - dev: false + html-url-attributes@3.0.0: {} - /html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - dev: false + html-void-elements@3.0.0: {} - /htmlparser2@9.1.0: - resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + htmlparser2@9.1.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 domutils: 3.1.0 entities: 4.5.0 - dev: false - /http-assert@1.5.0: - resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} - engines: {node: '>= 0.8'} + http-assert@1.5.0: dependencies: deep-equal: 1.0.1 http-errors: 1.8.1 - dev: false - /http-errors@1.8.1: - resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} - engines: {node: '>= 0.6'} + http-errors@1.8.1: dependencies: depd: 1.1.2 inherits: 2.0.4 setprototypeof: 1.2.0 statuses: 1.5.0 toidentifier: 1.0.1 - dev: false - /human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - dev: false + human-signals@2.1.0: {} - /human-signals@4.3.1: - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} - engines: {node: '>=14.18.0'} - dev: false + human-signals@4.3.1: {} - /iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - dev: false - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: false + ieee754@1.2.1: {} - /ignore@5.3.0: - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} - engines: {node: '>= 4'} + ignore@5.3.0: {} - /immutable@4.3.4: - resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} + immutable@4.3.4: {} - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - dev: true - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true + imurmurhash@0.1.4: {} - /indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - dev: true + indent-string@4.0.0: {} - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + inflight@1.0.6: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inherits@2.0.4: {} - /ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - dev: true + ini@1.3.8: {} - /inline-style-parser@0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} - dev: false + inline-style-parser@0.1.1: {} - /inline-style-parser@0.2.2: - resolution: {integrity: sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==} - dev: false + inline-style-parser@0.2.2: {} - /inquirer@8.2.6: - resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} - engines: {node: '>=12.0.0'} + inquirer@8.2.6: dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -5976,452 +12617,250 @@ packages: strip-ansi: 6.0.1 through: 2.3.8 wrap-ansi: 6.2.0 - dev: false - /internal-slot@1.0.6: - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} - engines: {node: '>= 0.4'} + internal-slot@1.0.6: dependencies: get-intrinsic: 1.2.2 hasown: 2.0.0 side-channel: 1.0.4 - dev: true - /internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} - dev: false + internmap@2.0.3: {} - /is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - dev: false + is-alphabetical@2.0.1: {} - /is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-alphanumerical@2.0.1: dependencies: is-alphabetical: 2.0.1 is-decimal: 2.0.1 - dev: false - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + is-array-buffer@3.0.2: dependencies: call-bind: 1.0.5 get-intrinsic: 1.2.2 is-typed-array: 1.1.12 - dev: true - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: true + is-arrayish@0.2.1: {} - /is-async-function@2.0.0: - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} - engines: {node: '>= 0.4'} + is-async-function@2.0.0: dependencies: has-tostringtag: 1.0.0 - dev: true - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-bigint@1.0.4: dependencies: has-bigints: 1.0.2 - dev: true - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.2.0 - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} + is-boolean-object@1.1.2: dependencies: call-bind: 1.0.5 has-tostringtag: 1.0.0 - dev: true - /is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - dev: false + is-buffer@2.0.5: {} - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true + is-callable@1.2.7: {} - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.13.1: dependencies: hasown: 2.0.0 - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} + is-date-object@1.0.5: dependencies: has-tostringtag: 1.0.0 - dev: true - /is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - dev: false + is-decimal@2.0.1: {} - /is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - dev: false + is-docker@2.2.1: {} - /is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - dev: false + is-docker@3.0.0: {} - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} + is-extglob@2.1.1: {} - /is-finalizationregistry@1.0.2: - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + is-finalizationregistry@1.0.2: dependencies: call-bind: 1.0.5 - dev: true - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + is-fullwidth-code-point@3.0.0: {} - /is-generator-function@1.0.10: - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} - engines: {node: '>= 0.4'} + is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.0 - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 - /is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - dev: false + is-hexadecimal@2.0.1: {} - /is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 - dev: false - /is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - dev: false + is-interactive@1.0.0: {} - /is-map@2.0.2: - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} - dev: true + is-map@2.0.2: {} - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - dev: true + is-negative-zero@2.0.2: {} - /is-node-process@1.2.0: - resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - dev: false + is-node-process@1.2.0: {} - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} + is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.0 - dev: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + is-number@7.0.0: {} - /is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - dev: true + is-obj@2.0.0: {} - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true + is-path-inside@3.0.3: {} - /is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - dev: true + is-plain-obj@1.1.0: {} - /is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - dev: false + is-plain-obj@4.1.0: {} - /is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - dev: true + is-plain-object@5.0.0: {} - /is-reference@3.0.2: - resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + is-reference@3.0.2: dependencies: '@types/estree': 1.0.5 - dev: false - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} + is-regex@1.1.4: dependencies: call-bind: 1.0.5 has-tostringtag: 1.0.0 - dev: true - /is-set@2.0.2: - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} - dev: true + is-set@2.0.2: {} - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + is-shared-array-buffer@1.0.2: dependencies: call-bind: 1.0.5 - dev: true - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - dev: false + is-stream@2.0.1: {} - /is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: false + is-stream@3.0.0: {} - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} + is-string@1.0.7: dependencies: has-tostringtag: 1.0.0 - dev: true - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} + is-symbol@1.0.4: dependencies: has-symbols: 1.0.3 - dev: true - /is-text-path@1.0.1: - resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} - engines: {node: '>=0.10.0'} + is-text-path@1.0.1: dependencies: text-extensions: 1.9.0 - dev: true - /is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} - engines: {node: '>= 0.4'} + is-typed-array@1.1.12: dependencies: which-typed-array: 1.1.13 - dev: true - /is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - dev: false + is-unicode-supported@0.1.0: {} - /is-weakmap@2.0.1: - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} - dev: true + is-weakmap@2.0.1: {} - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + is-weakref@1.0.2: dependencies: call-bind: 1.0.5 - dev: true - /is-weakset@2.0.2: - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + is-weakset@2.0.2: dependencies: call-bind: 1.0.5 get-intrinsic: 1.2.2 - dev: true - /is-what@4.1.16: - resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==} - engines: {node: '>=12.13'} - dev: true + is-what@4.1.16: {} - /is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 - dev: false - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: true + isarray@1.0.0: {} - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true + isarray@2.0.5: {} - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isexe@2.0.0: {} - /iterator.prototype@1.1.2: - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + iterator.prototype@1.1.2: dependencies: define-properties: 1.2.1 get-intrinsic: 1.2.2 has-symbols: 1.0.3 reflect.getprototypeof: 1.0.4 set-function-name: 2.0.1 - dev: true - /itertools@2.2.3: - resolution: {integrity: sha512-TV4TDJ2FrLxhRJDX/AgdyI76i6cHi2Z1hml/d+HLcGVHxmgfxsLpoQBN2ZE9OizPt10+VW+LamLfCDASlnxvNg==} - dev: false + itertools@2.2.3: {} - /jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} + jackspeak@2.3.6: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@4.0.0: {} - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true + js-yaml@4.1.0: dependencies: argparse: 2.0.1 - dev: true - /jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true - dev: false + jsesc@0.5.0: {} - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true + jsesc@2.5.2: {} - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true + json-buffer@3.0.1: {} - /json-parse-better-errors@1.0.2: - resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} - dev: true + json-parse-better-errors@1.0.2: {} - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true + json-parse-even-better-errors@2.3.1: {} - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true + json-schema-traverse@0.4.1: {} - /json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true + json-schema-traverse@1.0.0: {} - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true + json-stable-stringify-without-jsonify@1.0.1: {} - /json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - dev: true + json-stringify-safe@5.0.1: {} - /json5@1.0.2: - resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} - hasBin: true + json5@1.0.2: dependencies: minimist: 1.2.8 - dev: true - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true + json5@2.2.3: {} - /jsonc-parser@3.2.1: - resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} - dev: true + jsonc-parser@3.2.1: {} - /jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} - dev: true + jsonparse@1.3.1: {} - /jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} + 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 - dev: true - /keygrip@1.1.0: - resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} - engines: {node: '>= 0.6'} + keygrip@1.1.0: dependencies: tsscmp: 1.0.6 - dev: false - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@4.5.4: dependencies: json-buffer: 3.0.1 - dev: true - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - dev: true + kind-of@6.0.3: {} - /known-css-properties@0.29.0: - resolution: {integrity: sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==} - dev: true + known-css-properties@0.29.0: {} - /koa-compose@4.1.0: - resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} - dev: false + koa-compose@4.1.0: {} - /koa-connect@2.1.0: - resolution: {integrity: sha512-O9pcFafHk0oQsBevlbTBlB9co+2RUQJ4zCzu3qJPmGlGoeEZkne+7gWDkecqDPSbCtED6LmhlQladxs6NjOnMQ==} - dev: false + koa-connect@2.1.0: {} - /koa-convert@2.0.0: - resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} - engines: {node: '>= 10'} + koa-convert@2.0.0: dependencies: co: 4.6.0 koa-compose: 4.1.0 - dev: false - /koa@2.15.0: - resolution: {integrity: sha512-KEL/vU1knsoUvfP4MC4/GthpQrY/p6dzwaaGI6Rt4NQuFqkw3qrvsdYF5pz3wOfi7IGTvMPHC9aZIcUKYFNxsw==} - engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} + koa@2.15.0: dependencies: accepts: 1.3.8 cache-content-type: 1.0.1 @@ -6448,191 +12887,112 @@ packages: vary: 1.1.2 transitivePeerDependencies: - supports-color - dev: false - /language-subtag-registry@0.3.22: - resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} - dev: true + language-subtag-registry@0.3.22: {} - /language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} + language-tags@1.0.9: dependencies: language-subtag-registry: 0.3.22 - dev: true - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true + lines-and-columns@1.2.4: {} - /little-state-machine@4.8.0(react@18.2.0): - resolution: {integrity: sha512-xfi5+iDxTLhu0hbnNubUs+qoQQqxhtEZeObP5ELjUlHnl74bbasY7mOonsGQrAouyrbag3ebNLSse5xX1T7buQ==} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 + little-state-machine@4.8.0(react@18.2.0): dependencies: react: 18.2.0 - dev: true - /load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} + load-json-file@4.0.0: dependencies: graceful-fs: 4.2.11 parse-json: 4.0.0 pify: 3.0.0 strip-bom: 3.0.0 - dev: true - /locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} + locate-path@2.0.0: dependencies: p-locate: 2.0.0 path-exists: 3.0.0 - dev: true - /locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} + locate-path@3.0.0: dependencies: p-locate: 3.0.0 path-exists: 3.0.0 - dev: true - /locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - dev: true - /lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - dev: false + lodash-es@4.17.21: {} - /lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - dev: false + lodash.debounce@4.0.8: {} - /lodash.ismatch@4.4.0: - resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} - dev: true + lodash.ismatch@4.4.0: {} - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.merge@4.6.2: {} - /lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - dev: true + lodash.truncate@4.4.2: {} - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.21: {} - /log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} + log-symbols@4.1.0: dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 - dev: false - /longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - dev: false + longest-streak@3.1.0: {} - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 - /lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lower-case@2.0.2: dependencies: tslib: 2.6.2 - dev: true - /lru-cache@10.1.0: - resolution: {integrity: sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==} - engines: {node: 14 || >=16.14} - dev: true + lru-cache@10.1.0: {} - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + lru-cache@6.0.0: dependencies: yallist: 4.0.0 - dev: true - /lunr@2.3.9: - resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} - dev: true + lunr@2.3.9: {} - /magic-string@0.30.5: - resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} - engines: {node: '>=12'} + magic-string@0.30.5: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - /map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - dev: true + map-obj@1.0.1: {} - /map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - dev: true + map-obj@4.3.0: {} - /markdown-extensions@2.0.0: - resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} - engines: {node: '>=16'} - dev: false + markdown-extensions@2.0.0: {} - /markdown-table@3.0.3: - resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} - dev: false + markdown-table@3.0.3: {} - /marked@4.3.0: - resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} - engines: {node: '>= 12'} - hasBin: true - dev: true + marked@4.3.0: {} - /mathml-tag-names@2.1.3: - resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} - dev: true + mathml-tag-names@2.1.3: {} - /mdast-util-find-and-replace@3.0.1: - resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.3 escape-string-regexp: 5.0.0 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - dev: false - /mdast-util-from-markdown@2.0.0: - resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==} + mdast-util-from-markdown@2.0.0: dependencies: '@types/mdast': 4.0.3 '@types/unist': 3.0.2 @@ -6648,20 +13008,16 @@ packages: unist-util-stringify-position: 4.0.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-gfm-autolink-literal@2.0.0: - resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==} + mdast-util-gfm-autolink-literal@2.0.0: dependencies: '@types/mdast': 4.0.3 ccount: 2.0.1 devlop: 1.1.0 mdast-util-find-and-replace: 3.0.1 micromark-util-character: 2.0.1 - dev: false - /mdast-util-gfm-footnote@2.0.0: - resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + mdast-util-gfm-footnote@2.0.0: dependencies: '@types/mdast': 4.0.3 devlop: 1.1.0 @@ -6670,20 +13026,16 @@ packages: micromark-util-normalize-identifier: 2.0.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-from-markdown: 2.0.0 mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-gfm-table@2.0.0: - resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + mdast-util-gfm-table@2.0.0: dependencies: '@types/mdast': 4.0.3 devlop: 1.1.0 @@ -6692,10 +13044,8 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-gfm-task-list-item@2.0.0: - resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + mdast-util-gfm-task-list-item@2.0.0: dependencies: '@types/mdast': 4.0.3 devlop: 1.1.0 @@ -6703,10 +13053,8 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-gfm@3.0.0: - resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + mdast-util-gfm@3.0.0: dependencies: mdast-util-from-markdown: 2.0.0 mdast-util-gfm-autolink-literal: 2.0.0 @@ -6717,10 +13065,8 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-mdx-expression@2.0.0: - resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==} + mdast-util-mdx-expression@2.0.0: dependencies: '@types/estree-jsx': 1.0.3 '@types/hast': 3.0.3 @@ -6730,10 +13076,8 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-mdx-jsx@3.0.0: - resolution: {integrity: sha512-XZuPPzQNBPAlaqsTTgRrcJnyFbSOBovSadFgbFu8SnuNgm+6Bdx1K+IWoitsmj6Lq6MNtI+ytOqwN70n//NaBA==} + mdast-util-mdx-jsx@3.0.0: dependencies: '@types/estree-jsx': 1.0.3 '@types/hast': 3.0.3 @@ -6750,10 +13094,8 @@ packages: vfile-message: 4.0.2 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-mdx@3.0.0: - resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + mdast-util-mdx@3.0.0: dependencies: mdast-util-from-markdown: 2.0.0 mdast-util-mdx-expression: 2.0.0 @@ -6762,10 +13104,8 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-mdxjs-esm@2.0.1: - resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + mdast-util-mdxjs-esm@2.0.1: dependencies: '@types/estree-jsx': 1.0.3 '@types/hast': 3.0.3 @@ -6775,17 +13115,13 @@ packages: mdast-util-to-markdown: 2.1.0 transitivePeerDependencies: - supports-color - dev: false - /mdast-util-phrasing@4.0.0: - resolution: {integrity: sha512-xadSsJayQIucJ9n053dfQwVu1kuXg7jCTdYsMK8rqzKZh52nLfSH/k0sAxE0u+pj/zKZX+o5wB+ML5mRayOxFA==} + mdast-util-phrasing@4.0.0: dependencies: '@types/mdast': 4.0.3 unist-util-is: 6.0.0 - dev: false - /mdast-util-to-hast@13.1.0: - resolution: {integrity: sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA==} + mdast-util-to-hast@13.1.0: dependencies: '@types/hast': 3.0.3 '@types/mdast': 4.0.3 @@ -6796,10 +13132,8 @@ packages: unist-util-position: 5.0.0 unist-util-visit: 5.0.0 vfile: 6.0.1 - dev: false - /mdast-util-to-markdown@2.1.0: - resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + mdast-util-to-markdown@2.1.0: dependencies: '@types/mdast': 4.0.3 '@types/unist': 3.0.2 @@ -6809,39 +13143,22 @@ packages: micromark-util-decode-string: 2.0.0 unist-util-visit: 5.0.0 zwitch: 2.0.4 - dev: false - /mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdast-util-to-string@4.0.0: dependencies: '@types/mdast': 4.0.3 - dev: false - /mdn-data@2.0.28: - resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - dev: true + mdn-data@2.0.28: {} - /mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - dev: true + mdn-data@2.0.30: {} - /media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - dev: false + media-typer@0.3.0: {} - /memoize-one@5.2.1: - resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} - dev: false + memoize-one@5.2.1: {} - /meow@13.1.0: - resolution: {integrity: sha512-o5R/R3Tzxq0PJ3v3qcQJtSvSE9nKOLSAaDuuoMzDVuGTwHdccMWcYomh9Xolng2tjT6O/Y83d+0coVGof6tqmA==} - engines: {node: '>=18'} - dev: true + meow@13.1.0: {} - /meow@8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} + meow@8.1.2: dependencies: '@types/minimist': 1.2.5 camelcase-keys: 6.2.2 @@ -6854,18 +13171,12 @@ packages: trim-newlines: 3.0.1 type-fest: 0.18.1 yargs-parser: 20.2.9 - dev: true - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: false + merge-stream@2.0.0: {} - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + merge2@1.4.1: {} - /micromark-core-commonmark@2.0.0: - resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==} + micromark-core-commonmark@2.0.0: dependencies: decode-named-character-reference: 1.0.2 devlop: 1.1.0 @@ -6883,19 +13194,15 @@ packages: micromark-util-subtokenize: 2.0.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-extension-gfm-autolink-literal@2.0.0: - resolution: {integrity: sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==} + micromark-extension-gfm-autolink-literal@2.0.0: dependencies: micromark-util-character: 2.0.1 micromark-util-sanitize-uri: 2.0.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-extension-gfm-footnote@2.0.0: - resolution: {integrity: sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==} + micromark-extension-gfm-footnote@2.0.0: dependencies: devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -6905,10 +13212,8 @@ packages: micromark-util-sanitize-uri: 2.0.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-extension-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==} + micromark-extension-gfm-strikethrough@2.0.0: dependencies: devlop: 1.1.0 micromark-util-chunked: 2.0.0 @@ -6916,36 +13221,28 @@ packages: micromark-util-resolve-all: 2.0.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-extension-gfm-table@2.0.0: - resolution: {integrity: sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==} + micromark-extension-gfm-table@2.0.0: dependencies: devlop: 1.1.0 micromark-factory-space: 2.0.0 micromark-util-character: 2.0.1 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-extension-gfm-tagfilter@2.0.0: - resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + micromark-extension-gfm-tagfilter@2.0.0: dependencies: micromark-util-types: 2.0.0 - dev: false - /micromark-extension-gfm-task-list-item@2.0.1: - resolution: {integrity: sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==} + micromark-extension-gfm-task-list-item@2.0.1: dependencies: devlop: 1.1.0 micromark-factory-space: 2.0.0 micromark-util-character: 2.0.1 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-extension-gfm@3.0.0: - resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + micromark-extension-gfm@3.0.0: dependencies: micromark-extension-gfm-autolink-literal: 2.0.0 micromark-extension-gfm-footnote: 2.0.0 @@ -6955,10 +13252,8 @@ packages: micromark-extension-gfm-task-list-item: 2.0.1 micromark-util-combine-extensions: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-extension-mdx-expression@3.0.0: - resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==} + micromark-extension-mdx-expression@3.0.0: dependencies: '@types/estree': 1.0.5 devlop: 1.1.0 @@ -6968,10 +13263,8 @@ packages: micromark-util-events-to-acorn: 2.0.2 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-extension-mdx-jsx@3.0.0: - resolution: {integrity: sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==} + micromark-extension-mdx-jsx@3.0.0: dependencies: '@types/acorn': 4.0.6 '@types/estree': 1.0.5 @@ -6983,16 +13276,12 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 vfile-message: 4.0.2 - dev: false - /micromark-extension-mdx-md@2.0.0: - resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + micromark-extension-mdx-md@2.0.0: dependencies: micromark-util-types: 2.0.0 - dev: false - /micromark-extension-mdxjs-esm@3.0.0: - resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + micromark-extension-mdxjs-esm@3.0.0: dependencies: '@types/estree': 1.0.5 devlop: 1.1.0 @@ -7003,10 +13292,8 @@ packages: micromark-util-types: 2.0.0 unist-util-position-from-estree: 2.0.0 vfile-message: 4.0.2 - dev: false - /micromark-extension-mdxjs@3.0.0: - resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + micromark-extension-mdxjs@3.0.0: dependencies: acorn: 8.11.3 acorn-jsx: 5.3.2(acorn@8.11.3) @@ -7016,27 +13303,21 @@ packages: micromark-extension-mdxjs-esm: 3.0.0 micromark-util-combine-extensions: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-factory-destination@2.0.0: - resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + micromark-factory-destination@2.0.0: dependencies: micromark-util-character: 2.0.1 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-factory-label@2.0.0: - resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + micromark-factory-label@2.0.0: dependencies: devlop: 1.1.0 micromark-util-character: 2.0.1 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-factory-mdx-expression@2.0.1: - resolution: {integrity: sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==} + micromark-factory-mdx-expression@2.0.1: dependencies: '@types/estree': 1.0.5 devlop: 1.1.0 @@ -7046,82 +13327,60 @@ packages: micromark-util-types: 2.0.0 unist-util-position-from-estree: 2.0.0 vfile-message: 4.0.2 - dev: false - /micromark-factory-space@2.0.0: - resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + micromark-factory-space@2.0.0: dependencies: micromark-util-character: 2.0.1 micromark-util-types: 2.0.0 - dev: false - /micromark-factory-title@2.0.0: - resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + micromark-factory-title@2.0.0: dependencies: micromark-factory-space: 2.0.0 micromark-util-character: 2.0.1 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-factory-whitespace@2.0.0: - resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + micromark-factory-whitespace@2.0.0: dependencies: micromark-factory-space: 2.0.0 micromark-util-character: 2.0.1 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-util-character@2.0.1: - resolution: {integrity: sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==} + micromark-util-character@2.0.1: dependencies: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-util-chunked@2.0.0: - resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + micromark-util-chunked@2.0.0: dependencies: micromark-util-symbol: 2.0.0 - dev: false - /micromark-util-classify-character@2.0.0: - resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + micromark-util-classify-character@2.0.0: dependencies: micromark-util-character: 2.0.1 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-util-combine-extensions@2.0.0: - resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + micromark-util-combine-extensions@2.0.0: dependencies: micromark-util-chunked: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-util-decode-numeric-character-reference@2.0.1: - resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + micromark-util-decode-numeric-character-reference@2.0.1: dependencies: micromark-util-symbol: 2.0.0 - dev: false - /micromark-util-decode-string@2.0.0: - resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + micromark-util-decode-string@2.0.0: dependencies: decode-named-character-reference: 1.0.2 micromark-util-character: 2.0.1 micromark-util-decode-numeric-character-reference: 2.0.1 micromark-util-symbol: 2.0.0 - dev: false - /micromark-util-encode@2.0.0: - resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} - dev: false + micromark-util-encode@2.0.0: {} - /micromark-util-events-to-acorn@2.0.2: - resolution: {integrity: sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==} + micromark-util-events-to-acorn@2.0.2: dependencies: '@types/acorn': 4.0.6 '@types/estree': 1.0.5 @@ -7131,51 +13390,35 @@ packages: micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 vfile-message: 4.0.2 - dev: false - /micromark-util-html-tag-name@2.0.0: - resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} - dev: false + micromark-util-html-tag-name@2.0.0: {} - /micromark-util-normalize-identifier@2.0.0: - resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + micromark-util-normalize-identifier@2.0.0: dependencies: micromark-util-symbol: 2.0.0 - dev: false - /micromark-util-resolve-all@2.0.0: - resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + micromark-util-resolve-all@2.0.0: dependencies: micromark-util-types: 2.0.0 - dev: false - /micromark-util-sanitize-uri@2.0.0: - resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + micromark-util-sanitize-uri@2.0.0: dependencies: micromark-util-character: 2.0.1 micromark-util-encode: 2.0.0 micromark-util-symbol: 2.0.0 - dev: false - /micromark-util-subtokenize@2.0.0: - resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==} + micromark-util-subtokenize@2.0.0: dependencies: devlop: 1.1.0 micromark-util-chunked: 2.0.0 micromark-util-symbol: 2.0.0 micromark-util-types: 2.0.0 - dev: false - /micromark-util-symbol@2.0.0: - resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} - dev: false + micromark-util-symbol@2.0.0: {} - /micromark-util-types@2.0.0: - resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} - dev: false + micromark-util-types@2.0.0: {} - /micromark@4.0.0: - resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + micromark@4.0.0: dependencies: '@types/debug': 4.1.12 debug: 4.3.4 @@ -7196,102 +13439,53 @@ packages: micromark-util-types: 2.0.0 transitivePeerDependencies: - supports-color - dev: false - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + micromatch@4.0.5: dependencies: - braces: 3.0.2 + braces: 3.0.3 picomatch: 2.3.1 - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: false + mime-db@1.52.0: {} - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 - dev: false - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - dev: false + mimic-fn@2.1.0: {} - /mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - dev: false + mimic-fn@4.0.0: {} - /min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - dev: true + min-indent@1.0.1: {} - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 - dev: true - /minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} + minimatch@5.1.6: dependencies: brace-expansion: 2.0.1 - dev: true - /minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.3: dependencies: brace-expansion: 2.0.1 - dev: true - /minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} + minimist-options@4.1.0: dependencies: arrify: 1.0.1 is-plain-obj: 1.1.0 kind-of: 6.0.3 - dev: true - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true + minimist@1.2.8: {} - /minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} - engines: {node: '>=16 || 14 >=14.17'} - dev: true + minipass@7.0.4: {} - /modify-values@1.0.1: - resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} - engines: {node: '>=0.10.0'} - dev: true + modify-values@1.0.1: {} - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.2: {} - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true + ms@2.1.3: {} - /msw@2.1.4(typescript@5.3.3): - resolution: {integrity: sha512-YyIQpfLqAJf/O1kYPWBSbDqjgv71kRBmEbGLxkkai1Btcs/LcxKiAwT1My3COa9J/vTh9Ua41B/ReuiW9cXmkw==} - engines: {node: '>=18'} - hasBin: true - requiresBuild: true - peerDependencies: - typescript: '>= 4.7.x <= 5.3.x' - peerDependenciesMeta: - typescript: - optional: true + msw@2.1.4(typescript@5.3.3): dependencies: '@bundled-es-modules/cookie': 2.0.0 '@bundled-es-modules/statuses': 1.0.1 @@ -7310,209 +13504,132 @@ packages: path-to-regexp: 6.2.1 strict-event-emitter: 0.5.1 type-fest: 4.10.1 - typescript: 5.3.3 yargs: 17.7.2 - dev: false + optionalDependencies: + typescript: 5.3.3 - /mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - dev: false + mute-stream@0.0.8: {} - /nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + nanoid@3.3.7: {} - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true + natural-compare@1.4.0: {} - /negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - dev: false + negotiator@0.6.3: {} - /neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - dev: true + neo-async@2.6.2: {} - /no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + no-case@3.0.4: dependencies: lower-case: 2.0.2 tslib: 2.6.2 - dev: true - /node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + node-releases@2.0.14: {} - /normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + 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 - dev: true - /normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} + normalize-package-data@3.0.3: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.13.1 semver: 7.5.4 validate-npm-package-license: 3.0.4 - dev: true - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} + normalize-path@3.0.0: {} - /normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - dev: true + normalize-range@0.1.2: {} - /not@0.1.0: - resolution: {integrity: sha512-5PDmaAsVfnWUgTUbJ3ERwn7u79Z0dYxN9ErxCpVJJqe2RK0PJ3z+iFUxuqjwtlDDegXvtWoxD/3Fzxox7tFGWA==} - dev: false + not@0.1.0: {} - /npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 - dev: false - /npm-run-path@5.2.0: - resolution: {integrity: sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npm-run-path@5.2.0: dependencies: path-key: 4.0.0 - dev: false - /nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nth-check@2.1.1: dependencies: boolbase: 1.0.0 - /numbro@2.4.0: - resolution: {integrity: sha512-t6rVkO1CcKvffvOJJu/zMo70VIcQSR6w3AmIhfHGvmk4vHbNe6zHgomB0aWFAPZWM9JBVWBM0efJv9DBiRoSTA==} + numbro@2.4.0: dependencies: bignumber.js: 9.1.2 - dev: false - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} + object-assign@4.1.1: {} - /object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - dev: true + object-inspect@1.13.1: {} - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - dev: true + object-keys@1.1.1: {} - /object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} + object.assign@4.1.5: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 has-symbols: 1.0.3 object-keys: 1.1.1 - dev: true - /object.entries@1.1.7: - resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} - engines: {node: '>= 0.4'} + object.entries@1.1.7: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 - dev: true - /object.fromentries@2.0.7: - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} - engines: {node: '>= 0.4'} + object.fromentries@2.0.7: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 - dev: true - /object.groupby@1.0.1: - resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} + object.groupby@1.0.1: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 get-intrinsic: 1.2.2 - dev: true - /object.hasown@1.1.3: - resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} + object.hasown@1.1.3: dependencies: define-properties: 1.2.1 es-abstract: 1.22.3 - dev: true - /object.values@1.1.7: - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} - engines: {node: '>= 0.4'} + object.values@1.1.7: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 - dev: true - /on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} + on-finished@2.4.1: dependencies: ee-first: 1.1.1 - dev: false - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + once@1.4.0: dependencies: wrappy: 1.0.2 - dev: true - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 - dev: false - /onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} + onetime@6.0.0: dependencies: mimic-fn: 4.0.0 - dev: false - /only@0.0.2: - resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} - dev: false + only@0.0.2: {} - /open@9.1.0: - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} - engines: {node: '>=14.16'} + open@9.1.0: dependencies: default-browser: 4.0.0 define-lazy-prop: 3.0.0 is-inside-container: 1.0.0 is-wsl: 2.2.0 - dev: false - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} + optionator@0.9.3: dependencies: '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 @@ -7520,11 +13637,8 @@ packages: levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - dev: true - /ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} + ora@5.4.1: dependencies: bl: 4.1.0 chalk: 4.1.2 @@ -7535,82 +13649,48 @@ packages: log-symbols: 4.1.0 strip-ansi: 6.0.1 wcwidth: 1.0.1 - dev: false - /os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - dev: false + os-tmpdir@1.0.2: {} - /outvariant@1.4.2: - resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} - dev: false + outvariant@1.4.2: {} - /p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} + p-limit@1.3.0: dependencies: p-try: 1.0.0 - dev: true - /p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + p-limit@2.3.0: dependencies: p-try: 2.2.0 - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - dev: true - /p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} + p-locate@2.0.0: dependencies: p-limit: 1.3.0 - dev: true - /p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} + p-locate@3.0.0: dependencies: p-limit: 2.3.0 - dev: true - /p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + p-locate@4.1.0: dependencies: p-limit: 2.3.0 - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + p-locate@5.0.0: dependencies: p-limit: 3.1.0 - dev: true - /p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} - dev: true + p-try@1.0.0: {} - /p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + p-try@2.2.0: {} - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + parent-module@1.0.1: dependencies: callsites: 3.1.0 - dev: true - /parse-entities@4.0.1: - resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} + parse-entities@4.0.1: dependencies: '@types/unist': 2.0.10 character-entities: 2.0.2 @@ -7620,348 +13700,186 @@ packages: is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 - dev: false - /parse-json@4.0.0: - resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} - engines: {node: '>=4'} + parse-json@4.0.0: dependencies: error-ex: 1.3.2 json-parse-better-errors: 1.0.2 - dev: true - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.23.5 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - dev: true - /parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parse5@7.1.2: dependencies: entities: 4.5.0 - dev: false - /parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - dev: false + parseurl@1.3.3: {} - /path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - dev: true + path-exists@3.0.0: {} - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + path-exists@4.0.0: {} - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true + path-is-absolute@1.0.1: {} - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} + path-key@3.1.1: {} - /path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - dev: false + path-key@4.0.0: {} - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-parse@1.0.7: {} - /path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.10.1: dependencies: lru-cache: 10.1.0 minipass: 7.0.4 - dev: true - /path-to-regexp@6.2.1: - resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} - dev: false + path-to-regexp@6.2.1: {} - /path-type@3.0.0: - resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} - engines: {node: '>=4'} + path-type@3.0.0: dependencies: pify: 3.0.0 - dev: true - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: true + path-type@4.0.0: {} - /path-type@5.0.0: - resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} - engines: {node: '>=12'} - dev: false + path-type@5.0.0: {} - /periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + periscopic@3.1.0: dependencies: '@types/estree': 1.0.5 estree-walker: 3.0.3 is-reference: 3.0.2 - dev: false - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.0.0: {} - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + picomatch@2.3.1: {} - /pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - dev: true + pify@2.3.0: {} - /pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - dev: true + pify@3.0.0: {} - /pngjs@5.0.0: - resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} - engines: {node: '>=10.13.0'} - dev: false + pngjs@5.0.0: {} - /postcss-media-query-parser@0.2.3: - resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - dev: true + postcss-media-query-parser@0.2.3: {} - /postcss-resolve-nested-selector@0.1.1: - resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} - dev: true + postcss-resolve-nested-selector@0.1.1: {} - /postcss-safe-parser@7.0.0(postcss@8.4.33): - resolution: {integrity: sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==} - engines: {node: '>=18.0'} - peerDependencies: - postcss: ^8.4.31 + postcss-safe-parser@7.0.0(postcss@8.4.33): dependencies: postcss: 8.4.33 - dev: true - /postcss-scss@4.0.9(postcss@8.4.33): - resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.4.29 + postcss-scss@4.0.9(postcss@8.4.33): dependencies: postcss: 8.4.33 - dev: true - /postcss-selector-parser@6.0.15: - resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==} - engines: {node: '>=4'} + postcss-selector-parser@6.0.15: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - dev: true - /postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: true + postcss-value-parser@4.2.0: {} - /postcss@8.4.33: - resolution: {integrity: sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==} - engines: {node: ^10 || ^12 || >=14} + postcss@8.4.33: dependencies: nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true + prelude-ls@1.2.1: {} - /prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} + prettier-linter-helpers@1.0.0: dependencies: fast-diff: 1.3.0 - dev: true - /prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true + prettier@2.8.8: {} - /prettier@3.2.4: - resolution: {integrity: sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==} - engines: {node: '>=14'} - hasBin: true - dev: true + prettier@3.2.4: {} - /prism-react-renderer@2.3.1(react@18.2.0): - resolution: {integrity: sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==} - peerDependencies: - react: '>=16.0.0' + prism-react-renderer@2.3.1(react@18.2.0): dependencies: '@types/prismjs': 1.26.3 clsx: 2.1.0 react: 18.2.0 - dev: false - /process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: true + process-nextick-args@2.0.1: {} - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 react-is: 16.13.1 - /property-information@6.4.1: - resolution: {integrity: sha512-OHYtXfu5aI2sS2LWFSN5rgJjrQ4pCy8i1jubJLe2QvMF8JJ++HXTUIVWFLfXJoaOfvYYjk2SN8J2wFUWIGXT4w==} - dev: false + property-information@6.4.1: {} - /proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - dev: false + proxy-from-env@1.1.0: {} - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - dev: true + punycode@2.3.1: {} - /q@1.5.1: - resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} - engines: {node: '>=0.6.0', teleport: '>=0.2.0'} - dev: true + q@1.5.1: {} - /qr.js@0.0.0: - resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==} - dev: false + qr.js@0.0.0: {} - /qrcode@1.5.3: - resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==} - engines: {node: '>=10.13.0'} - hasBin: true + qrcode@1.5.3: dependencies: dijkstrajs: 1.0.3 encode-utf8: 1.0.3 pngjs: 5.0.0 yargs: 15.4.1 - dev: false - /query-string@8.1.0: - resolution: {integrity: sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==} - engines: {node: '>=14.16'} + query-string@8.1.0: dependencies: decode-uri-component: 0.4.1 filter-obj: 5.1.0 split-on-first: 3.0.0 - dev: false - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-microtask@1.2.3: {} - /quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - dev: true + quick-lru@4.0.1: {} - /radash@11.0.0: - resolution: {integrity: sha512-CRWxTFTDff0IELGJ/zz58yY4BDgyI14qSM5OLNKbCItJrff7m7dXbVF0kWYVCXQtPb3SXIVhXvAImH6eT7VLSg==} - engines: {node: '>=14.18.0'} - dev: false + radash@11.0.0: {} - /react-click-away-listener@2.2.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-p63JRQtK9d085+QHUJ2Pje22P/N4tEaXsS2x7tbbptriQqZ9o8xEk7G1JrxwND5YmEVc/VO4fC3+cSBsqqgLUQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-click-away-listener@2.2.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: false - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 + react-dom@18.2.0(react@18.2.0): dependencies: loose-envify: 1.4.0 react: 18.2.0 scheduler: 0.23.0 - /react-hook-form@7.49.3(react@18.2.0): - resolution: {integrity: sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==} - engines: {node: '>=18', pnpm: '8'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 + react-hook-form@7.49.3(react@18.2.0): dependencies: react: 18.2.0 - dev: false - /react-hotkeys-hook@4.4.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-wzZmqb/Obr0ds9Myc1sIFPJ52GA/Eeg/vXBWV0HA1LvHlVAW5Va3KB0q6EZNlNSHQWscWZ2K8+6w0GYSie2o7A==} - peerDependencies: - react: '>=16.8.1' - react-dom: '>=16.8.1' + react-hotkeys-hook@4.4.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: false - /react-idle-timer@5.7.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ==} - peerDependencies: - react: '>=16' - react-dom: '>=16' + react-idle-timer@5.7.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: false - /react-inspector@6.0.2(react@18.2.0): - resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} - peerDependencies: - react: ^16.8.4 || ^17.0.0 || ^18.0.0 + react-inspector@6.0.2(react@18.2.0): dependencies: react: 18.2.0 - dev: false - /react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@16.13.1: {} - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: false + react-is@18.2.0: {} - /react-lifecycles-compat@3.0.4: - resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} - dev: false + react-lifecycles-compat@3.0.4: {} - /react-loading-skeleton@3.3.1(react@18.2.0): - resolution: {integrity: sha512-NilqqwMh2v9omN7LteiDloEVpFyMIa0VGqF+ukqp0ncVlYu1sKYbYGX9JEl+GtOT9TKsh04zCHAbavnQ2USldA==} - peerDependencies: - react: '>=16.8.0' + react-loading-skeleton@3.3.1(react@18.2.0): dependencies: react: 18.2.0 - dev: false - /react-markdown@9.0.1(@types/react@18.2.48)(react@18.2.0): - resolution: {integrity: sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==} - peerDependencies: - '@types/react': '>=18' - react: '>=18' + react-markdown@9.0.1(@types/react@18.2.48)(react@18.2.0): dependencies: '@types/hast': 3.0.3 '@types/react': 18.2.48 @@ -7977,81 +13895,42 @@ packages: vfile: 6.0.1 transitivePeerDependencies: - supports-color - dev: false - /react-property@2.0.2: - resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==} - dev: false + react-property@2.0.2: {} - /react-qr-code@2.0.12(react@18.2.0): - resolution: {integrity: sha512-k+pzP5CKLEGBRwZsDPp98/CAJeXlsYRHM2iZn1Sd5Th/HnKhIZCSg27PXO58zk8z02RaEryg+60xa4vyywMJwg==} - peerDependencies: - react: ^16.x || ^17.x || ^18.x - react-native-svg: '*' - peerDependenciesMeta: - react-native-svg: - optional: true + react-qr-code@2.0.12(react@18.2.0): dependencies: prop-types: 15.8.1 qr.js: 0.0.0 react: 18.2.0 - dev: false - /react-refresh@0.14.0: - resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} - engines: {node: '>=0.10.0'} - dev: false + react-refresh@0.14.0: {} - /react-router-dom@6.21.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' + react-router-dom@6.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@remix-run/router': 1.14.2 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-router: 6.21.3(react@18.2.0) - dev: false - /react-router@6.21.3(react@18.2.0): - resolution: {integrity: sha512-a0H638ZXULv1OdkmiK6s6itNhoy33ywxmUFT/xtSoVyf9VnC7n7+VT4LjVzdIHSaF5TIh9ylUgxMXksHTgGrKg==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' + react-router@6.21.3(react@18.2.0): dependencies: '@remix-run/router': 1.14.2 react: 18.2.0 - dev: false - /react-simple-animate@3.5.2(react-dom@18.2.0): - resolution: {integrity: sha512-xLE65euP920QMTOmv5haPlml+hmOPDkbIr5WeF7ADIXWBYt5kW/vwpNfWg8EKMab8aeDxIZ6QjffVh8v2dUyhg==} - peerDependencies: - react-dom: ^16.8.0 || ^17 || ^18 + react-simple-animate@3.5.2(react-dom@18.2.0(react@18.2.0)): dependencies: react-dom: 18.2.0(react@18.2.0) - dev: true - /react-smooth@2.0.5(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-BMP2Ad42tD60h0JW6BFaib+RJuV5dsXJK9Baxiv/HlNFjvRLqA9xrNKxVWnUIZPQfzUwGXIlU/dSYLU+54YGQA==} - peerDependencies: - prop-types: ^15.6.0 - react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-smooth@2.0.5(prop-types@15.8.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: fast-equals: 5.0.1 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - react-transition-group: 2.9.0(react-dom@18.2.0)(react@18.2.0) - dev: false + react-transition-group: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) - /react-transition-group@2.9.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==} - peerDependencies: - react: '>=15.0.0' - react-dom: '>=15.0.0' + react-transition-group@2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: dom-helpers: 3.4.0 loose-envify: 1.4.0 @@ -8059,75 +13938,48 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-lifecycles-compat: 3.0.4 - dev: false - /react-virtualized-auto-sizer@1.0.21(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-RedZxj452+ITLfqIrR02BjvCaXV63YVIcVrvmruDZXFpJGazg4gHNs1AShPGVLvEuLGZdZ9AtkGKhWvzEujL8g==} - peerDependencies: - react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 - react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 + react-virtualized-auto-sizer@1.0.21(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: false - /react-window@1.8.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==} - engines: {node: '>8.0.0'} - peerDependencies: - react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-window@1.8.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: '@babel/runtime': 7.23.8 memoize-one: 5.2.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: false - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} + react@18.2.0: dependencies: loose-envify: 1.4.0 - /read-pkg-up@3.0.0: - resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} - engines: {node: '>=4'} + read-pkg-up@3.0.0: dependencies: find-up: 2.1.0 read-pkg: 3.0.0 - dev: true - /read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 read-pkg: 5.2.0 type-fest: 0.8.1 - dev: true - /read-pkg@3.0.0: - resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} - engines: {node: '>=4'} + read-pkg@3.0.0: dependencies: load-json-file: 4.0.0 normalize-package-data: 2.5.0 path-type: 3.0.0 - dev: true - /read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} + 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 - dev: true - /readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -8136,35 +13988,22 @@ packages: safe-buffer: 5.1.2 string_decoder: 1.1.1 util-deprecate: 1.0.2 - dev: true - /readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 - /recharts-scale@0.4.5: - resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + recharts-scale@0.4.5: dependencies: decimal.js-light: 2.5.1 - dev: false - /recharts@2.10.4(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-/Q7/wdf8bW91lN3NEeCjL9RWfaiXQViJFgdnas4Eix/I8B9HAI3tHHK/CW/zDfgRMh4fzW1zlfjoz1IAapLO1Q==} - engines: {node: '>=14'} - peerDependencies: - prop-types: ^15.6.0 - react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + recharts@2.10.4(prop-types@15.8.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: clsx: 2.1.0 eventemitter3: 4.0.7 @@ -8173,23 +14012,17 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-is: 16.13.1 - react-smooth: 2.0.5(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) + react-smooth: 2.0.5(prop-types@15.8.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) recharts-scale: 0.4.5 tiny-invariant: 1.3.1 victory-vendor: 36.8.2 - dev: false - /redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} + redent@3.0.0: dependencies: indent-string: 4.0.0 strip-indent: 3.0.0 - dev: true - /reflect.getprototypeof@1.0.4: - resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} - engines: {node: '>= 0.4'} + reflect.getprototypeof@1.0.4: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 @@ -8197,40 +14030,26 @@ packages: get-intrinsic: 1.2.2 globalthis: 1.0.3 which-builtin-type: 1.1.3 - dev: true - /regenerate-unicode-properties@10.1.1: - resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} - engines: {node: '>=4'} + regenerate-unicode-properties@10.1.1: dependencies: regenerate: 1.4.2 - dev: false - /regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - dev: false + regenerate@1.4.2: {} - /regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regenerator-runtime@0.14.1: {} - /regenerator-transform@0.15.2: - resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + regenerator-transform@0.15.2: dependencies: '@babel/runtime': 7.23.8 - dev: false - /regexp.prototype.flags@1.5.1: - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} - engines: {node: '>= 0.4'} + regexp.prototype.flags@1.5.1: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 set-function-name: 2.0.1 - dev: true - /regexpu-core@5.3.2: - resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} - engines: {node: '>=4'} + regexpu-core@5.3.2: dependencies: '@babel/regjsgen': 0.8.0 regenerate: 1.4.2 @@ -8238,34 +14057,25 @@ packages: regjsparser: 0.9.1 unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.1.0 - dev: false - /regjsparser@0.9.1: - resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} - hasBin: true + regjsparser@0.9.1: dependencies: jsesc: 0.5.0 - dev: false - /rehype-class-names@1.0.14: - resolution: {integrity: sha512-eFBt6Qxb7K77y6P82tUtN9rKpU7guWlaK4XA4RrrSFHkUTCvr2D3cgb9OR5d4t1AaGOvR59FH9nRwUnbpn9AEg==} + rehype-class-names@1.0.14: dependencies: '@types/hast': 3.0.3 hast-util-classnames: 3.0.0 hast-util-select: 6.0.2 unified: 10.1.2 - dev: false - /rehype-raw@7.0.0: - resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.3 hast-util-raw: 9.0.2 vfile: 6.0.1 - dev: false - /remark-gfm@4.0.0: - resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} + remark-gfm@4.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-gfm: 3.0.0 @@ -8275,19 +14085,15 @@ packages: unified: 11.0.4 transitivePeerDependencies: - supports-color - dev: false - /remark-mdx@3.0.0: - resolution: {integrity: sha512-O7yfjuC6ra3NHPbRVxfflafAj3LTwx3b73aBvkEFU5z4PsD6FD4vrqJAkE5iNGLz71GdjXfgRqm3SQ0h0VuE7g==} + remark-mdx@3.0.0: dependencies: mdast-util-mdx: 3.0.0 micromark-extension-mdxjs: 3.0.0 transitivePeerDependencies: - supports-color - dev: false - /remark-parse@11.0.0: - resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-from-markdown: 2.0.0 @@ -8295,118 +14101,70 @@ packages: unified: 11.0.4 transitivePeerDependencies: - supports-color - dev: false - /remark-rehype@11.1.0: - resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==} + remark-rehype@11.1.0: dependencies: '@types/hast': 3.0.3 '@types/mdast': 4.0.3 mdast-util-to-hast: 13.1.0 unified: 11.0.4 vfile: 6.0.1 - dev: false - /remark-stringify@11.0.0: - resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remark-stringify@11.0.0: dependencies: '@types/mdast': 4.0.3 mdast-util-to-markdown: 2.1.0 unified: 11.0.4 - dev: false - /remove-accents@0.4.2: - resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} - dev: true + remove-accents@0.4.2: {} - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + require-directory@2.1.1: {} - /require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - dev: true + require-from-string@2.0.2: {} - /require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: false + require-main-filename@2.0.0: {} - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true + resolve-from@4.0.0: {} - /resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - dev: true + resolve-from@5.0.0: {} - /resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true + 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: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} - hasBin: true + 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 - dev: true - /restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} + restore-cursor@3.1.0: dependencies: onetime: 5.1.2 signal-exit: 3.0.7 - dev: false - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + reusify@1.0.4: {} - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true + rimraf@3.0.2: dependencies: glob: 7.2.3 - dev: true - /rimraf@5.0.5: - resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} - engines: {node: '>=14'} - hasBin: true + rimraf@5.0.5: dependencies: glob: 10.3.10 - dev: true - /rollup-plugin-preserve-directives@0.3.1(rollup@4.9.6): - resolution: {integrity: sha512-Jn1gWU7G55A1sU6eFpXmwknfBasF0XbBzRqsE6nqrb/gun+mGV7nx++CwOSGPJQpFzFqvKm5U4XNKo3LTLi4Hg==} - peerDependencies: - rollup: 2.x || 3.x || 4.x + rollup-plugin-preserve-directives@0.3.1(rollup@4.9.6): dependencies: magic-string: 0.30.5 rollup: 4.9.6 - dev: true - /rollup@2.79.1: - resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} - engines: {node: '>=10.0.0'} - hasBin: true + rollup@2.79.1: optionalDependencies: fsevents: 2.3.3 - dev: true - /rollup@4.9.6: - resolution: {integrity: sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true + rollup@4.9.6: dependencies: '@types/estree': 1.0.5 optionalDependencies: @@ -8425,258 +14183,157 @@ packages: '@rollup/rollup-win32-x64-msvc': 4.9.6 fsevents: 2.3.3 - /run-applescript@5.0.0: - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} - engines: {node: '>=12'} + run-applescript@5.0.0: dependencies: execa: 5.1.1 - dev: false - /run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - dev: false + run-async@2.4.1: {} - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - /rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + rxjs@7.8.1: dependencies: tslib: 2.6.2 - /safe-array-concat@1.1.0: - resolution: {integrity: sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==} - engines: {node: '>=0.4'} + safe-array-concat@1.1.0: dependencies: call-bind: 1.0.5 get-intrinsic: 1.2.2 has-symbols: 1.0.3 isarray: 2.0.5 - dev: true - /safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: true + safe-buffer@5.1.2: {} - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-buffer@5.2.1: {} - /safe-regex-test@1.0.2: - resolution: {integrity: sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==} - engines: {node: '>= 0.4'} + safe-regex-test@1.0.2: dependencies: call-bind: 1.0.5 get-intrinsic: 1.2.2 is-regex: 1.1.4 - dev: true - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: false + safer-buffer@2.1.2: {} - /sass@1.70.0: - resolution: {integrity: sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==} - engines: {node: '>=14.0.0'} - hasBin: true + sass@1.70.0: dependencies: chokidar: 3.5.3 immutable: 4.3.4 source-map-js: 1.0.2 - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + scheduler@0.23.0: dependencies: loose-envify: 1.4.0 - /semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - dev: true + semver@5.7.2: {} - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true + semver@6.3.1: {} - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true + semver@7.5.4: dependencies: lru-cache: 6.0.0 - dev: true - /set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: false + set-blocking@2.0.0: {} - /set-function-length@1.2.0: - resolution: {integrity: sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==} - engines: {node: '>= 0.4'} + set-function-length@1.2.0: dependencies: define-data-property: 1.1.1 function-bind: 1.1.2 get-intrinsic: 1.2.2 gopd: 1.0.1 has-property-descriptors: 1.0.1 - dev: true - /set-function-name@2.0.1: - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} - engines: {node: '>= 0.4'} + set-function-name@2.0.1: dependencies: define-data-property: 1.1.1 functions-have-names: 1.2.3 has-property-descriptors: 1.0.1 - dev: true - /setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - dev: false + setprototypeof@1.2.0: {} - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} + shebang-regex@3.0.0: {} - /shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} - dev: true + shell-quote@1.8.1: {} - /shiki@0.14.7: - resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} + shiki@0.14.7: dependencies: ansi-sequence-parser: 1.1.1 jsonc-parser: 3.2.1 vscode-oniguruma: 1.7.0 vscode-textmate: 8.0.0 - dev: true - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + side-channel@1.0.4: dependencies: call-bind: 1.0.5 get-intrinsic: 1.2.2 object-inspect: 1.13.1 - dev: true - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: false + signal-exit@3.0.7: {} - /signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - dev: true + signal-exit@4.1.0: {} - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - dev: true + slash@3.0.0: {} - /slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - dev: false + slash@5.1.0: {} - /slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} + slice-ansi@4.0.0: dependencies: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - dev: true - /snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + snake-case@3.0.4: dependencies: dot-case: 3.0.4 tslib: 2.6.2 - dev: true - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} + source-map-js@1.0.2: {} - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 source-map: 0.6.1 - /source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - dev: true + source-map@0.5.7: {} - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} + source-map@0.6.1: {} - /source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - dev: false + source-map@0.7.4: {} - /space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - dev: false + space-separated-tokens@2.0.2: {} - /spawn-command@0.0.2: - resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} - dev: true + spawn-command@0.0.2: {} - /spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 spdx-license-ids: 3.0.16 - dev: true - /spdx-exceptions@2.4.0: - resolution: {integrity: sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==} - dev: true + spdx-exceptions@2.4.0: {} - /spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.4.0 spdx-license-ids: 3.0.16 - dev: true - /spdx-license-ids@3.0.16: - resolution: {integrity: sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==} - dev: true + spdx-license-ids@3.0.16: {} - /split-on-first@3.0.0: - resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==} - engines: {node: '>=12'} - dev: false + split-on-first@3.0.0: {} - /split2@3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} + split2@3.2.2: dependencies: readable-stream: 3.6.2 - dev: true - /split@1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + split@1.0.1: dependencies: through: 2.3.8 - dev: true - /standard-version@9.5.0: - resolution: {integrity: sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==} - engines: {node: '>=10'} - hasBin: true + standard-version@9.5.0: dependencies: chalk: 2.4.2 conventional-changelog: 3.1.25 @@ -8692,40 +14349,26 @@ packages: semver: 7.5.4 stringify-package: 1.0.1 yargs: 16.2.0 - dev: true - /statuses@1.5.0: - resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} - engines: {node: '>= 0.6'} - dev: false + statuses@1.5.0: {} - /statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - dev: false + statuses@2.0.1: {} - /strict-event-emitter@0.5.1: - resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - dev: false + strict-event-emitter@0.5.1: {} - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} + 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: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} + string-width@5.1.2: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - /string.prototype.matchall@4.0.10: - resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} + string.prototype.matchall@4.0.10: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 @@ -8736,180 +14379,103 @@ packages: regexp.prototype.flags: 1.5.1 set-function-name: 2.0.1 side-channel: 1.0.4 - dev: true - /string.prototype.trim@1.2.8: - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} - engines: {node: '>= 0.4'} + string.prototype.trim@1.2.8: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 - dev: true - /string.prototype.trimend@1.0.7: - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + string.prototype.trimend@1.0.7: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 - dev: true - /string.prototype.trimstart@1.0.7: - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + string.prototype.trimstart@1.0.7: dependencies: call-bind: 1.0.5 define-properties: 1.2.1 es-abstract: 1.22.3 - dev: true - /string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 - dev: true - /string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 - /stringify-entities@4.0.3: - resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==} + stringify-entities@4.0.3: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - dev: false - /stringify-package@1.0.1: - resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} - deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json - dev: true + stringify-package@1.0.1: {} - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - /strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} + strip-ansi@7.1.0: dependencies: ansi-regex: 6.0.1 - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true + strip-bom@3.0.0: {} - /strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - dev: false + strip-final-newline@2.0.0: {} - /strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - dev: false + strip-final-newline@3.0.0: {} - /strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 - dev: true - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true + strip-json-comments@3.1.1: {} - /style-to-js@1.1.10: - resolution: {integrity: sha512-VC7MBJa+y0RZhpnLKDPmVRLRswsASLmixkiZ5R8xZpNT9VyjeRzwnXd2pBzAWdgSGv/pCNNH01gPCCUsB9exYg==} + style-to-js@1.1.10: dependencies: style-to-object: 1.0.5 - dev: false - /style-to-object@0.4.4: - resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + style-to-object@0.4.4: dependencies: inline-style-parser: 0.1.1 - dev: false - /style-to-object@1.0.5: - resolution: {integrity: sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ==} + style-to-object@1.0.5: dependencies: inline-style-parser: 0.2.2 - dev: false - /stylelint-config-prettier-scss@1.0.0(stylelint@16.2.0): - resolution: {integrity: sha512-Gr2qLiyvJGKeDk0E/+awNTrZB/UtNVPLqCDOr07na/sLekZwm26Br6yYIeBYz3ulsEcQgs5j+2IIMXCC+wsaQA==} - engines: {node: 14.* || 16.* || >= 18} - hasBin: true - peerDependencies: - stylelint: '>=15.0.0' + stylelint-config-prettier-scss@1.0.0(stylelint@16.2.0(typescript@5.3.3)): dependencies: stylelint: 16.2.0(typescript@5.3.3) - dev: true - /stylelint-config-recommended-scss@14.0.0(postcss@8.4.33)(stylelint@16.2.0): - resolution: {integrity: sha512-HDvpoOAQ1RpF+sPbDOT2Q2/YrBDEJDnUymmVmZ7mMCeNiFSdhRdyGEimBkz06wsN+HaFwUh249gDR+I9JR7Onw==} - engines: {node: '>=18.12.0'} - peerDependencies: - postcss: ^8.3.3 - stylelint: ^16.0.2 - peerDependenciesMeta: - postcss: - optional: true + stylelint-config-recommended-scss@14.0.0(postcss@8.4.33)(stylelint@16.2.0(typescript@5.3.3)): dependencies: - postcss: 8.4.33 postcss-scss: 4.0.9(postcss@8.4.33) stylelint: 16.2.0(typescript@5.3.3) - stylelint-config-recommended: 14.0.0(stylelint@16.2.0) - stylelint-scss: 6.1.0(stylelint@16.2.0) - dev: true + stylelint-config-recommended: 14.0.0(stylelint@16.2.0(typescript@5.3.3)) + stylelint-scss: 6.1.0(stylelint@16.2.0(typescript@5.3.3)) + optionalDependencies: + postcss: 8.4.33 - /stylelint-config-recommended@14.0.0(stylelint@16.2.0): - resolution: {integrity: sha512-jSkx290CglS8StmrLp2TxAppIajzIBZKYm3IxT89Kg6fGlxbPiTiyH9PS5YUuVAFwaJLl1ikiXX0QWjI0jmgZQ==} - engines: {node: '>=18.12.0'} - peerDependencies: - stylelint: ^16.0.0 + stylelint-config-recommended@14.0.0(stylelint@16.2.0(typescript@5.3.3)): dependencies: stylelint: 16.2.0(typescript@5.3.3) - dev: true - /stylelint-config-standard-scss@13.0.0(postcss@8.4.33)(stylelint@16.2.0): - resolution: {integrity: sha512-WaLvkP689qSYUpJQPCo30TFJSSc3VzvvoWnrgp+7PpVby5o8fRUY1cZcP0sePZfjrFl9T8caGhcKg0GO34VDiQ==} - engines: {node: '>=18.12.0'} - peerDependencies: - postcss: ^8.3.3 - stylelint: ^16.1.0 - peerDependenciesMeta: - postcss: - optional: true + stylelint-config-standard-scss@13.0.0(postcss@8.4.33)(stylelint@16.2.0(typescript@5.3.3)): dependencies: - postcss: 8.4.33 stylelint: 16.2.0(typescript@5.3.3) - stylelint-config-recommended-scss: 14.0.0(postcss@8.4.33)(stylelint@16.2.0) - stylelint-config-standard: 36.0.0(stylelint@16.2.0) - dev: true + stylelint-config-recommended-scss: 14.0.0(postcss@8.4.33)(stylelint@16.2.0(typescript@5.3.3)) + stylelint-config-standard: 36.0.0(stylelint@16.2.0(typescript@5.3.3)) + optionalDependencies: + postcss: 8.4.33 - /stylelint-config-standard@36.0.0(stylelint@16.2.0): - resolution: {integrity: sha512-3Kjyq4d62bYFp/Aq8PMKDwlgUyPU4nacXsjDLWJdNPRUgpuxALu1KnlAHIj36cdtxViVhXexZij65yM0uNIHug==} - engines: {node: '>=18.12.0'} - peerDependencies: - stylelint: ^16.1.0 + stylelint-config-standard@36.0.0(stylelint@16.2.0(typescript@5.3.3)): dependencies: stylelint: 16.2.0(typescript@5.3.3) - stylelint-config-recommended: 14.0.0(stylelint@16.2.0) - dev: true + stylelint-config-recommended: 14.0.0(stylelint@16.2.0(typescript@5.3.3)) - /stylelint-scss@6.1.0(stylelint@16.2.0): - resolution: {integrity: sha512-kCfK8TQzthGwb4vaZniZgxRsVbCM4ZckmT1b/H5m4FU3I8Dz0id9llKsy1NMp3XXqC8+OPD4rVKtUbSxXlJb5g==} - engines: {node: '>=18.12.0'} - peerDependencies: - stylelint: ^16.0.2 + stylelint-scss@6.1.0(stylelint@16.2.0(typescript@5.3.3)): dependencies: known-css-properties: 0.29.0 postcss-media-query-parser: 0.2.3 @@ -8917,16 +14483,12 @@ packages: postcss-selector-parser: 6.0.15 postcss-value-parser: 4.2.0 stylelint: 16.2.0(typescript@5.3.3) - dev: true - /stylelint@16.2.0(typescript@5.3.3): - resolution: {integrity: sha512-gwqU5AkIb52wrAzzn+359S3NIJDMl02TXLUaV2tzA/L6jUdpTwNt+MCxHlc8+Hb2bUHlYVo92YeSIryF2gJthA==} - engines: {node: '>=18.12.0'} - hasBin: true + stylelint@16.2.0(typescript@5.3.3): dependencies: '@csstools/css-parser-algorithms': 2.5.0(@csstools/css-tokenizer@2.2.3) '@csstools/css-tokenizer': 2.2.3 - '@csstools/media-query-list-parser': 2.1.7(@csstools/css-parser-algorithms@2.5.0)(@csstools/css-tokenizer@2.2.3) + '@csstools/media-query-list-parser': 2.1.7(@csstools/css-parser-algorithms@2.5.0(@csstools/css-tokenizer@2.2.3))(@csstools/css-tokenizer@2.2.3) '@csstools/selector-specificity': 3.0.1(postcss-selector-parser@6.0.15) balanced-match: 2.0.0 colord: 2.9.3 @@ -8965,62 +14527,37 @@ packages: transitivePeerDependencies: - supports-color - typescript - dev: true - /stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - dev: true + stylis@4.2.0: {} - /superjson@1.13.3: - resolution: {integrity: sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==} - engines: {node: '>=10'} + superjson@1.13.3: dependencies: copy-anything: 3.0.5 - dev: true - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + supports-color@5.5.0: dependencies: has-flag: 3.0.0 - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-color@8.1.1: dependencies: has-flag: 4.0.0 - dev: true - /supports-hyperlinks@3.0.0: - resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} - engines: {node: '>=14.18'} + supports-hyperlinks@3.0.0: dependencies: has-flag: 4.0.0 supports-color: 7.2.0 - dev: true - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + supports-preserve-symlinks-flag@1.0.0: {} - /svg-parser@2.0.4: - resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - dev: true + svg-parser@2.0.4: {} - /svg-tags@1.0.0: - resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - dev: true + svg-tags@1.0.0: {} - /svgo@3.2.0: - resolution: {integrity: sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==} - engines: {node: '>=14.0.0'} - hasBin: true + svgo@3.2.0: dependencies: '@trysound/sax': 0.2.0 commander: 7.2.0 @@ -9029,344 +14566,189 @@ packages: css-what: 6.1.0 csso: 5.0.5 picocolors: 1.0.0 - dev: true - /synckit@0.8.8: - resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} - engines: {node: ^14.18.0 || >=16.0.0} + synckit@0.8.8: dependencies: '@pkgr/core': 0.1.1 tslib: 2.6.2 - dev: true - /systemjs@6.14.3: - resolution: {integrity: sha512-hQv45irdhXudAOr8r6SVSpJSGtogdGZUbJBRKCE5nsIS7tsxxvnIHqT4IOPWj+P+HcSzeWzHlGCGpmhPDIKe+w==} - dev: false + systemjs@6.14.3: {} - /tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - dev: false + tabbable@6.2.0: {} - /table@6.8.1: - resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} - engines: {node: '>=10.0.0'} + table@6.8.1: dependencies: ajv: 8.12.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true - /terser@5.27.0: - resolution: {integrity: sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==} - engines: {node: '>=10'} - hasBin: true + terser@5.27.0: dependencies: '@jridgewell/source-map': 0.3.5 acorn: 8.11.3 commander: 2.20.3 source-map-support: 0.5.21 - /text-extensions@1.9.0: - resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} - engines: {node: '>=0.10'} - dev: true + text-extensions@1.9.0: {} - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true + text-table@0.2.0: {} - /through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + through2@2.0.5: dependencies: readable-stream: 2.3.8 xtend: 4.0.2 - dev: true - /through2@4.0.2: - resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + through2@4.0.2: dependencies: readable-stream: 3.6.2 - dev: true - /through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + through@2.3.8: {} - /tiny-invariant@1.3.1: - resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} - dev: false + tiny-invariant@1.3.1: {} - /titleize@3.0.0: - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} - engines: {node: '>=12'} - dev: false + titleize@3.0.0: {} - /tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 - dev: false - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} + to-fast-properties@2.0.0: {} - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - /toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - dev: false + toidentifier@1.0.1: {} - /tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - dev: true + tree-kill@1.2.2: {} - /trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - dev: false + trim-lines@3.0.1: {} - /trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - dev: true + trim-newlines@3.0.1: {} - /trough@2.1.0: - resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} - dev: false + trough@2.1.0: {} - /ts-api-utils@1.0.3(typescript@5.3.3): - resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} - engines: {node: '>=16.13.0'} - peerDependencies: - typescript: '>=4.2.0' + ts-api-utils@1.0.3(typescript@5.3.3): dependencies: typescript: 5.3.3 - dev: true - /tsconfck@3.0.1(typescript@5.3.3): - resolution: {integrity: sha512-7ppiBlF3UEddCLeI1JRx5m2Ryq+xk4JrZuq4EuYXykipebaq1dV0Fhgr1hb7CkmHt32QSgOZlcqVLEtHBG4/mg==} - engines: {node: ^18 || >=20} - hasBin: true - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - dependencies: + tsconfck@3.0.1(typescript@5.3.3): + optionalDependencies: typescript: 5.3.3 - dev: false - /tsconfig-paths@3.15.0: - resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 json5: 1.0.2 minimist: 1.2.8 strip-bom: 3.0.0 - dev: true - /tslib@2.4.0: - resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} - dev: false + tslib@2.4.0: {} - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tslib@2.6.2: {} - /tsscmp@1.0.6: - resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} - engines: {node: '>=0.6.x'} - dev: false + tsscmp@1.0.6: {} - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - dev: true - - /type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - dev: true - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - dev: false - - /type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - dev: true - - /type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - dev: true - - /type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - dev: false - - /type-fest@4.10.1: - resolution: {integrity: sha512-7ZnJYTp6uc04uYRISWtiX3DSKB/fxNQT0B5o1OUeCqiQiwF+JC9+rJiZIDrPrNCLLuTqyQmh4VdQqh/ZOkv9MQ==} - engines: {node: '>=16'} - dev: false - - /type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} + + type-fest@0.18.1: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@0.6.0: {} + + type-fest@0.8.1: {} + + type-fest@2.19.0: {} + + type-fest@4.10.1: {} + + type-is@1.6.18: dependencies: media-typer: 0.3.0 mime-types: 2.1.35 - dev: false - /typed-array-buffer@1.0.0: - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} - engines: {node: '>= 0.4'} + typed-array-buffer@1.0.0: dependencies: call-bind: 1.0.5 get-intrinsic: 1.2.2 is-typed-array: 1.1.12 - dev: true - /typed-array-byte-length@1.0.0: - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} - engines: {node: '>= 0.4'} + typed-array-byte-length@1.0.0: dependencies: call-bind: 1.0.5 for-each: 0.3.3 has-proto: 1.0.1 is-typed-array: 1.1.12 - dev: true - /typed-array-byte-offset@1.0.0: - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} - engines: {node: '>= 0.4'} + typed-array-byte-offset@1.0.0: dependencies: available-typed-arrays: 1.0.5 call-bind: 1.0.5 for-each: 0.3.3 has-proto: 1.0.1 is-typed-array: 1.1.12 - dev: true - /typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + typed-array-length@1.0.4: dependencies: call-bind: 1.0.5 for-each: 0.3.3 is-typed-array: 1.1.12 - dev: true - /typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - dev: true + typedarray@0.0.6: {} - /typedoc@0.25.7(typescript@5.3.3): - resolution: {integrity: sha512-m6A6JjQRg39p2ZVRIN3NKXgrN8vzlHhOS+r9ymUYtcUP/TIQPvWSq7YgE5ZjASfv5Vd5BW5xrir6Gm2XNNcOow==} - engines: {node: '>= 16'} - hasBin: true - peerDependencies: - typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x + typedoc@0.25.7(typescript@5.3.3): dependencies: lunr: 2.3.9 marked: 4.3.0 minimatch: 9.0.3 shiki: 0.14.7 typescript: 5.3.3 - dev: true - /typesafe-i18n@5.26.2(typescript@5.3.3): - resolution: {integrity: sha512-2QAriFmiY5JwUAJtG7yufoE/XZ1aFBY++wj7YFS2yo89a3jLBfKoWSdq5JfQYk1V2BS7V2c/u+KEcaCQoE65hw==} - hasBin: true - peerDependencies: - typescript: '>=3.5.1' + typesafe-i18n@5.26.2(typescript@5.3.3): dependencies: typescript: 5.3.3 - dev: false - /typescript-eslint-language-service@5.0.5(@typescript-eslint/parser@6.19.1)(eslint@8.56.0)(typescript@5.3.3): - resolution: {integrity: sha512-b7gWXpwSTqMVKpPX3WttNZEyVAMKs/2jsHKF79H+qaD6mjzCyU5jboJe/lOZgLJD+QRsXCr0GjIVxvl5kI1NMw==} - peerDependencies: - '@typescript-eslint/parser': '>= 5.0.0' - eslint: '>= 8.0.0' - typescript: '>= 4.0.0' + typescript-eslint-language-service@5.0.5(@typescript-eslint/parser@6.19.1(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3): dependencies: '@typescript-eslint/parser': 6.19.1(eslint@8.56.0)(typescript@5.3.3) eslint: 8.56.0 typescript: 5.3.3 - dev: true - /typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} - engines: {node: '>=14.17'} - hasBin: true + typescript@5.3.3: {} - /uglify-js@3.17.4: - resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} - engines: {node: '>=0.8.0'} - hasBin: true - requiresBuild: true - dev: true + uglify-js@3.17.4: optional: true - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + unbox-primitive@1.0.2: dependencies: call-bind: 1.0.5 has-bigints: 1.0.2 has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - dev: true - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@5.26.5: {} - /unicode-canonical-property-names-ecmascript@2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} - engines: {node: '>=4'} - dev: false + unicode-canonical-property-names-ecmascript@2.0.0: {} - /unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} + unicode-match-property-ecmascript@2.0.0: dependencies: unicode-canonical-property-names-ecmascript: 2.0.0 unicode-property-aliases-ecmascript: 2.1.0 - dev: false - /unicode-match-property-value-ecmascript@2.1.0: - resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} - engines: {node: '>=4'} - dev: false + unicode-match-property-value-ecmascript@2.1.0: {} - /unicode-property-aliases-ecmascript@2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} - engines: {node: '>=4'} - dev: false + unicode-property-aliases-ecmascript@2.1.0: {} - /unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} - dev: false + unicorn-magic@0.1.0: {} - /unified@10.1.2: - resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} + unified@10.1.2: dependencies: '@types/unist': 2.0.10 bail: 2.0.2 @@ -9375,10 +14757,8 @@ packages: is-plain-obj: 4.1.0 trough: 2.1.0 vfile: 5.3.7 - dev: false - /unified@11.0.4: - resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} + unified@11.0.4: dependencies: '@types/unist': 3.0.2 bail: 2.0.2 @@ -9387,169 +14767,110 @@ packages: is-plain-obj: 4.1.0 trough: 2.1.0 vfile: 6.0.1 - dev: false - /unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-is@6.0.0: dependencies: '@types/unist': 3.0.2 - dev: false - /unist-util-position-from-estree@2.0.0: - resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + unist-util-position-from-estree@2.0.0: dependencies: '@types/unist': 3.0.2 - dev: false - /unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + unist-util-position@5.0.0: dependencies: '@types/unist': 3.0.2 - dev: false - /unist-util-remove-position@5.0.0: - resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + unist-util-remove-position@5.0.0: dependencies: '@types/unist': 3.0.2 unist-util-visit: 5.0.0 - dev: false - /unist-util-stringify-position@3.0.3: - resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + unist-util-stringify-position@3.0.3: dependencies: '@types/unist': 2.0.10 - dev: false - /unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.2 - dev: false - /unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + unist-util-visit-parents@6.0.1: dependencies: '@types/unist': 3.0.2 unist-util-is: 6.0.0 - dev: false - /unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unist-util-visit@5.0.0: dependencies: '@types/unist': 3.0.2 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - dev: false - /untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - dev: false + untildify@4.0.0: {} - /update-browserslist-db@1.0.13(browserslist@4.22.2): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' + update-browserslist-db@1.0.13(browserslist@4.22.2): dependencies: browserslist: 4.22.2 escalade: 3.1.1 picocolors: 1.0.0 - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - dev: true - /use-breakpoint@4.0.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Fa5Duxv3BY3bw8kmj/lmryTETXVUmBQeYJpBgPJ2yJRiIaGVG8rlMNKQE+JS2rywCZHWaggVUz+ytbr7sH/yyg==} - peerDependencies: - react: '>=18' - react-dom: '>=18' + use-breakpoint@4.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0): dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: false - /use-deep-compare-effect@1.8.1(react@18.2.0): - resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==} - engines: {node: '>=10', npm: '>=6'} - peerDependencies: - react: '>=16.13' + use-deep-compare-effect@1.8.1(react@18.2.0): dependencies: '@babel/runtime': 7.23.8 dequal: 2.0.3 react: 18.2.0 - dev: true - /use-sync-external-store@1.2.0(react@18.2.0): - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + use-sync-external-store@1.2.0(react@18.2.0): dependencies: react: 18.2.0 - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util-deprecate@1.0.2: {} - /uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - dev: true + uuid@8.3.2: {} - /validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - dev: true - /vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - dev: false + vary@1.1.2: {} - /vfile-location@5.0.2: - resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} + vfile-location@5.0.2: dependencies: '@types/unist': 3.0.2 vfile: 6.0.1 - dev: false - /vfile-message@3.1.4: - resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + vfile-message@3.1.4: dependencies: '@types/unist': 2.0.10 unist-util-stringify-position: 3.0.3 - dev: false - /vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} + vfile-message@4.0.2: dependencies: '@types/unist': 3.0.2 unist-util-stringify-position: 4.0.0 - dev: false - /vfile@5.3.7: - resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + vfile@5.3.7: dependencies: '@types/unist': 2.0.10 is-buffer: 2.0.5 unist-util-stringify-position: 3.0.3 vfile-message: 3.1.4 - dev: false - /vfile@6.0.1: - resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + vfile@6.0.1: dependencies: '@types/unist': 3.0.2 unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - dev: false - /victory-vendor@36.8.2: - resolution: {integrity: sha512-NfSQi7ISCdBbDpn3b6rg+8RpFZmWIM9mcks48BbogHE2F6h1XKdA34oiCKP5hP1OGvTotDRzsexiJKzrK4Exuw==} + victory-vendor@36.8.2: dependencies: '@types/d3-array': 3.2.1 '@types/d3-ease': 3.0.2 @@ -9565,114 +14886,60 @@ packages: d3-shape: 3.2.0 d3-time: 3.1.0 d3-timer: 3.0.1 - dev: false - /vite-plugin-eslint@1.8.1(eslint@8.56.0)(vite@5.0.12): - resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} - peerDependencies: - eslint: '>=7' - vite: '>=2' + vite-plugin-eslint@1.8.1(eslint@8.56.0)(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)): dependencies: '@rollup/pluginutils': 4.2.1 '@types/eslint': 8.56.2 eslint: 8.56.0 rollup: 2.79.1 vite: 5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0) - dev: true - /vite-plugin-package-version@1.1.0(vite@5.0.12): - resolution: {integrity: sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==} - peerDependencies: - vite: '>=2.0.0-beta.69' + vite-plugin-package-version@1.1.0(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)): dependencies: vite: 5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0) - dev: true - /vite-tsconfig-paths@4.3.1(typescript@5.3.3)(vite@5.0.12): - resolution: {integrity: sha512-cfgJwcGOsIxXOLU/nELPny2/LUD/lcf1IbfyeKTv2bsupVbTH/xpFtdQlBmIP1GEK2CjjLxYhFfB+QODFAx5aw==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true + vite-tsconfig-paths@4.3.1(typescript@5.3.3)(vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0)): dependencies: debug: 4.3.4 globrex: 0.1.2 tsconfck: 3.0.1(typescript@5.3.3) + optionalDependencies: vite: 5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0) transitivePeerDependencies: - supports-color - typescript - dev: false - /vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0): - resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true + vite@5.0.12(@types/node@20.11.7)(sass@1.70.0)(terser@5.27.0): dependencies: - '@types/node': 20.11.7 esbuild: 0.19.12 postcss: 8.4.33 rollup: 4.9.6 - sass: 1.70.0 - terser: 5.27.0 optionalDependencies: + '@types/node': 20.11.7 fsevents: 2.3.3 + sass: 1.70.0 + terser: 5.27.0 - /vscode-oniguruma@1.7.0: - resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} - dev: true + vscode-oniguruma@1.7.0: {} - /vscode-textmate@8.0.0: - resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} - dev: true + vscode-textmate@8.0.0: {} - /wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + wcwidth@1.0.1: dependencies: defaults: 1.0.4 - dev: false - /web-namespaces@2.0.1: - resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - dev: false + web-namespaces@2.0.1: {} - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + 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 - dev: true - /which-builtin-type@1.1.3: - resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} - engines: {node: '>= 0.4'} + which-builtin-type@1.1.3: dependencies: function.prototype.name: 1.1.6 has-tostringtag: 1.0.0 @@ -9686,152 +14953,87 @@ packages: which-boxed-primitive: 1.0.2 which-collection: 1.0.1 which-typed-array: 1.1.13 - dev: true - /which-collection@1.0.1: - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + which-collection@1.0.1: dependencies: is-map: 2.0.2 is-set: 2.0.2 is-weakmap: 2.0.1 is-weakset: 2.0.2 - dev: true - /which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - dev: false + which-module@2.0.1: {} - /which-typed-array@1.1.13: - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} - engines: {node: '>= 0.4'} + which-typed-array@1.1.13: dependencies: available-typed-arrays: 1.0.5 call-bind: 1.0.5 for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 - dev: true - /which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true + which@1.3.1: dependencies: isexe: 2.0.0 - dev: true - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + which@2.0.2: dependencies: isexe: 2.0.0 - /widest-line@4.0.1: - resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} - engines: {node: '>=12'} + widest-line@4.0.1: dependencies: string-width: 5.1.2 - dev: false - /wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - dev: true + wordwrap@1.0.0: {} - /wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: false - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + 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: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} + wrap-ansi@8.1.0: dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true + wrappy@1.0.2: {} - /write-file-atomic@5.0.1: - resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + write-file-atomic@5.0.1: dependencies: imurmurhash: 0.1.4 signal-exit: 4.1.0 - dev: true - /ws@8.5.0: - resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false + ws@8.5.0: {} - /xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: true + xtend@4.0.2: {} - /y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: false + y18n@4.0.3: {} - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + y18n@5.0.8: {} - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@3.1.1: {} - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true + yallist@4.0.0: {} - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - dev: true + yaml@1.10.2: {} - /yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} + yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 decamelize: 1.2.0 - dev: false - /yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - dev: true + yargs-parser@20.2.9: {} - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} + yargs-parser@21.1.1: {} - /yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} + yargs@15.4.1: dependencies: cliui: 6.0.0 decamelize: 1.2.0 @@ -9844,11 +15046,8 @@ packages: which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 - dev: false - /yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} + yargs@16.2.0: dependencies: cliui: 7.0.4 escalade: 3.1.1 @@ -9857,11 +15056,8 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 - dev: true - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} + yargs@17.7.2: dependencies: cliui: 8.0.1 escalade: 3.1.1 @@ -9871,40 +15067,17 @@ packages: y18n: 5.0.8 yargs-parser: 21.1.1 - /ylru@1.3.2: - resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} - engines: {node: '>= 4.0.0'} - dev: false + ylru@1.3.2: {} - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true + yocto-queue@0.1.0: {} - /zod@3.22.4: - resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} - dev: false + zod@3.22.4: {} - /zustand@4.5.0(@types/react@18.2.48)(react@18.2.0): - resolution: {integrity: sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A==} - engines: {node: '>=12.7.0'} - peerDependencies: - '@types/react': '>=16.8' - immer: '>=9.0.6' - react: '>=16.8' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true + zustand@4.5.0(@types/react@18.2.48)(react@18.2.0): dependencies: + use-sync-external-store: 1.2.0(react@18.2.0) + optionalDependencies: '@types/react': 18.2.48 react: 18.2.0 - use-sync-external-store: 1.2.0(react@18.2.0) - dev: false - /zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - dev: false + zwitch@2.0.4: {} diff --git a/web/src/components/AppLoader.tsx b/web/src/components/AppLoader.tsx index 0e87e4a4d8..55b94d0636 100644 --- a/web/src/components/AppLoader.tsx +++ b/web/src/components/AppLoader.tsx @@ -28,8 +28,9 @@ export const AppLoader = () => { const appSettings = useAppStore((state) => state.settings); const { getAppInfo, + getEnterpriseStatus, user: { getMe }, - settings: { getEssentialSettings }, + settings: { getEssentialSettings, getEnterpriseSettings }, } = useApi(); const [userLoading, setUserLoading] = useState(true); const { setLocale } = useI18nContext(); @@ -67,6 +68,31 @@ export const AppLoader = () => { enabled: !isUndefined(currentUser), }); + useQuery([QueryKeys.FETCH_ENTERPRISE_STATUS], getEnterpriseStatus, { + onSuccess: (status) => { + setAppStore({ + enterprise_status: status, + }); + }, + onError: (err) => { + // FIXME: Add a proper error message + toaster.error(LL.messages.errorVersion()); + console.error(err); + }, + refetchOnWindowFocus: false, + retry: false, + }); + + useQuery([QueryKeys.FETCH_ENTERPRISE_SETTINGS], getEnterpriseSettings, { + onSuccess: (settings) => { + setAppStore({ enterprise_settings: settings }); + }, + onError: (err) => { + console.error(err); + }, + refetchOnWindowFocus: true, + retry: false, + }); const { isLoading: settingsLoading, data: essentialSettings } = useQuery( [QueryKeys.FETCH_ESSENTIAL_SETTINGS], getEssentialSettings, diff --git a/web/src/components/Navigation/components/ApplicationVersion/ApplicationVersion.tsx b/web/src/components/Navigation/components/ApplicationVersion/ApplicationVersion.tsx index 3427546394..a6c88d7b6c 100644 --- a/web/src/components/Navigation/components/ApplicationVersion/ApplicationVersion.tsx +++ b/web/src/components/Navigation/components/ApplicationVersion/ApplicationVersion.tsx @@ -14,20 +14,19 @@ export const ApplicationVersion = ({ isOpen }: Props) => { return (

- {isOpen && <>{LL.navigation.copyright()}} + <>{isOpen ? LL.navigation.copyright() : '©'}  - {!isOpen && '\u00A9'}teonite + teonite

{version && (

- + {isOpen ? LL.navigation.version.open({ version }) - : LL.navigation.version.closed({ version })} + : LL.navigation.version.closed({ + version: version.split('-')[0], + })}

)} diff --git a/web/src/i18n/en/index.ts b/web/src/i18n/en/index.ts index fac618863b..a94620912d 100644 --- a/web/src/i18n/en/index.ts +++ b/web/src/i18n/en/index.ts @@ -33,6 +33,7 @@ const en: BaseTranslation = { success: 'Operation succeeded', errorVersion: 'Failed to get application version.', insecureContext: 'Context is not secure.', + details: 'Details:', clipboard: { error: 'Clipboard is not accessible.', success: 'Content copied to clipboard.', @@ -374,7 +375,8 @@ const en: BaseTranslation = { label: 'Phone', }, enableEnrollment: { - label: 'Use enrollment process', + label: 'Use user self-enrollment process', + link: 'more information here', }, }, }, @@ -441,6 +443,7 @@ const en: BaseTranslation = { title: 'Add device', helpers: { setupOpt: `You can add a device using this wizard. Opt for our native application "defguard" or any other WireGuard client. If you're unsure, we recommend using defguard for simplicity.`, + client: `Please download defguard desktop client here and then follow this guide.`, }, messages: { deviceAdded: 'Device added', @@ -540,6 +543,17 @@ const en: BaseTranslation = { messages: { deleteApp: 'App and all tokens deleted.', }, + warningModals: { + title: 'Warning', + content: { + usernameChange: `Changing the username has a significant impact on services the user has logged into using Defguard. After changing it, the user may lose access to applications (since they will not recognize them). Are you sure you want to proceed?`, + emailChange: `If you are using external OpenID Connect (OIDC) providers to authenticate users, changing a user's email address may have a significant impact on their ability to log in to Defguard. Are you sure you want to proceed?`, + }, + buttons: { + proceed: 'Proceed', + cancel: 'Cancel', + }, + }, fields: { username: { label: 'Username', @@ -807,10 +821,10 @@ const en: BaseTranslation = { enrollment: 'Enrollment', support: 'Support', }, - copyright: 'Copyright \u00A9 2023 ', + copyright: 'Copyright ©2023-2024', version: { open: 'Application version: {version: string}', - closed: 'v {version: string}', + closed: 'v{version: string}', }, }, form: { @@ -850,6 +864,7 @@ const en: BaseTranslation = { number: 'Expected a valid number.', minimumValue: `Minimum value of {value: number} not reached.`, maximumValue: 'Maximum value of {value: number} exceeded.', + tooManyBadLoginAttempts: `Too many bad login attempts. Please try again in a few minutes.`, }, floatingErrors: { title: 'Please correct the following:', @@ -889,11 +904,19 @@ const en: BaseTranslation = { smtp: 'SMTP', global: 'Global settings', ldap: 'LDAP', + openid: 'OpenID', + enterprise: 'Enterprise features', }, messages: { editSuccess: 'Settings updated', challengeSuccess: 'Challenge message changed', }, + enterpriseOnly: { + title: 'This feature is available only in Defguard Enterprise.', + currentExpired: 'Your current license has expired.', + subtitle: 'To learn more, visit our ', + website: 'website', + }, ldapSettings: { title: 'LDAP Settings', form: { @@ -910,6 +933,7 @@ const en: BaseTranslation = { ldap_group_member_attr: 'Group Member Attribute', ldap_group_obj_class: 'Group Object Class', }, + delete: 'Delete configuration', }, test: { title: 'Test LDAP Connection', @@ -920,6 +944,46 @@ const en: BaseTranslation = { }, }, }, + openIdSettings: { + general: { + title: 'External OpenID Settings', + helper: 'Here you can change general OpenID behavior in your Defguard instance.', + createAccount: { + label: + 'Automatically create user account when logging in for the first time through external OpenID.', + helper: + 'If this option is enabled, Defguard automatically creates new accounts for users who log in for the first time using an external OpenID provider. Otherwise, the user account must first be created by an administrator.', + }, + }, + form: { + title: 'External OpenID Client Settings', + helper: + 'Here you can configure the OpenID client settings with values provided by your external OpenID provider.', + custom: 'Custom', + documentation: 'Documentation', + delete: 'Delete provider', + labels: { + provider: { + label: 'Provider', + helper: + 'Select your OpenID provider. You can use custom provider and fill in the base URL by yourself.', + }, + client_id: { + label: 'Client ID', + helper: 'Client ID provided by your OpenID provider.', + }, + client_secret: { + label: 'Client Secret', + helper: 'Client Secret provided by your OpenID provider.', + }, + base_url: { + label: 'Base URL', + helper: + 'Base URL of your OpenID provider, e.g. https://accounts.google.com. Make sure to check our documentation for more information and examples.', + }, + }, + }, + }, modulesVisibility: { header: 'Modules Visibility', helper: `

@@ -1000,6 +1064,58 @@ const en: BaseTranslation = { `, }, + license: { + header: 'Enterprise', + helpers: { + enterpriseHeader: { + text: 'Here you can manage your Defguard Enterprise version license.', + link: 'To learn more about Defguard Enterprise, visit our webiste.', + }, + licenseKey: { + text: 'Enter your Defguard Enterprise license key below. You should receive it via email after purchasing the license.', + link: 'You can purchase the license here.', + }, + }, + form: { + title: 'License', + fields: { + key: { + label: 'License key', + placeholder: 'Your Defguard license key', + }, + }, + }, + licenseInfo: { + title: 'License information', + noLicense: 'No license', + types: { + subscription: { + label: 'Subscription', + helper: 'A license that automatically renews at regular intervals', + }, + offline: { + label: 'Offline', + helper: + 'The license is valid until the expiry date and does not automatically renew', + }, + }, + fields: { + status: { + label: 'Status', + active: 'Active', + expired: 'Expired', + subscriptionHelper: + 'A subscription license is considered valid for some time after the expiration date to account for possible automatic payment delays.', + }, + type: { + label: 'Type', + }, + validUntil: { + label: 'Valid until', + }, + }, + }, + }, smtp: { form: { title: 'SMTP configuration', @@ -1038,6 +1154,7 @@ const en: BaseTranslation = { submit: 'Save changes', }, }, + delete: 'Delete configuration', testForm: { title: 'Send test email', fields: { @@ -1110,6 +1227,27 @@ const en: BaseTranslation = { }, }, }, + enterprise: { + header: 'Enterprise Features', + helper: '

Here you can change enterprise settings.

', + fields: { + deviceManagement: { + label: "Disable users' ability to manage their devices", + helper: + "When this option is enabled, only users in the Admin group can manage devices in user profile (it's disabled for all other users)", + }, + disableAllTraffic: { + label: 'Disable the option to route all traffic through VPN', + helper: + 'When this option is enabled, users will not be able to route all traffic through the VPN using the defguard client.', + }, + manualConfig: { + label: "Disable users' ability to manually configure WireGuard client", + helper: + "When this option is enabled, users won't be able to view or download configuration for the manual WireGuard client setup. Only the Defguard desktop client configuration will be available.", + }, + }, + }, }, openidOverview: { pageTitle: 'OpenID Apps', @@ -1424,32 +1562,47 @@ const en: BaseTranslation = { }, }, gatewaySetup: { - header: 'Gateway server setup', + header: { + main: 'Gateway server setup', + dockerBasedGatewaySetup: `Docker Based Gateway Setup`, + fromPackage: `From Package`, + oneLineInstall: `One Line Install`, + }, card: { title: 'Docker based gateway setup', + authToken: `Authentication Token`, + }, + button: { + availablePackages: `Available Packages`, }, controls: { status: 'Check connection status', }, messages: { - runCommand: ` -

- Defguard requires to deploy a gateway node to control wireguard VPN on the vpn server. - More details can be found in the documentation. + runCommand: `Defguard requires to deploy a gateway node to control wireguard VPN on the vpn server. + More details can be found in the [documentation]({setupGatewayDocs:string}). There are several ways to deploy the gateway server, - below is a Docker based example, for other examples please visit documentation. -

`, - createNetwork: ` -

- Please create the network before running the gateway process. -

`, - noConnection: `

No connection established, please run provided command.

`, - connected: `

Gateway connected.

`, + below is a Docker based example, for other examples please visit [documentation]({setupGatewayDocs:string}).`, + createNetwork: `Please create the network before running the gateway process.`, + noConnection: `No connection established, please run provided command.`, + connected: `Gateway connected.`, statusError: 'Failed to get gateway status', + oneLineInstall: `If you are doing one line install: https://defguard.gitbook.io/defguard/admin-and-features/setting-up-your-instance/one-line-install + you don't need to do anything.`, + fromPackage: `Install the package available at https://github.com/DefGuard/gateway/releases/latest and configure \`/etc/defguard/gateway.toml\` + according to the [documentation]({setupGatewayDocs:string}).`, + authToken: `Token below is required to authenticate and configure the gateway node. Ensure you keep this token secure and follow the deployment instructions + provided in the [documentation]({setupGatewayDocs:string}) to successfully set up the gateway server. + For more details and exact steps, please refer to the [documentation]({setupGatewayDocs:string}).`, + dockerBasedGatewaySetup: `Below is a Docker based example. For more details and exact steps, please refer to the [documentation]({setupGatewayDocs:string}).`, }, }, loginPage: { pageTitle: 'Enter your credentials', + callback: { + return: 'Go back to login', + error: 'An error occurred during external OpenID login', + }, mfa: { title: 'Two-factor authentication', controls: { @@ -1604,7 +1757,7 @@ const en: BaseTranslation = { }, }, messageBox: - 'Enrollment is process by which the new employee will be able to confirm their new account, create a password and configurate VPN device. In this panel you can custom messages for it.', + 'Enrollment is a process by which a new employee will be able to activate their new account, create a password and configure a VPN device. You can customize it here.', settings: { welcomeMessage: { title: 'Welcome message', diff --git a/web/src/i18n/i18n-types.ts b/web/src/i18n/i18n-types.ts index edfee8010f..524f84697c 100644 --- a/web/src/i18n/i18n-types.ts +++ b/web/src/i18n/i18n-types.ts @@ -7,6 +7,7 @@ export type BaseLocale = 'en' export type Locales = | 'en' + | 'ko' | 'pl' export type Translation = RootTranslation @@ -117,6 +118,10 @@ type RootTranslation = { * C​o​n​t​e​x​t​ ​i​s​ ​n​o​t​ ​s​e​c​u​r​e​. */ insecureContext: string + /** + * D​e​t​a​i​l​s​: + */ + details: string clipboard: { /** * C​l​i​p​b​o​a​r​d​ ​i​s​ ​n​o​t​ ​a​c​c​e​s​s​i​b​l​e​. @@ -875,9 +880,13 @@ type RootTranslation = { } enableEnrollment: { /** - * U​s​e​ ​e​n​r​o​l​l​m​e​n​t​ ​p​r​o​c​e​s​s + * U​s​e​ ​u​s​e​r​ ​s​e​l​f​-​e​n​r​o​l​l​m​e​n​t​ ​p​r​o​c​e​s​s */ label: string + /** + * <​a​ ​h​r​e​f​=​"​h​t​t​p​s​:​/​/​d​e​f​g​u​a​r​d​.​g​i​t​b​o​o​k​.​i​o​/​d​e​f​g​u​a​r​d​/​h​e​l​p​/​e​n​r​o​l​l​m​e​n​t​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​m​o​r​e​ ​i​n​f​o​r​m​a​t​i​o​n​ ​h​e​r​e​<​/​a​> + */ + link: string } } } @@ -1026,6 +1035,10 @@ type RootTranslation = { * Y​o​u​ ​c​a​n​ ​a​d​d​ ​a​ ​d​e​v​i​c​e​ ​u​s​i​n​g​ ​t​h​i​s​ ​w​i​z​a​r​d​.​ ​O​p​t​ ​f​o​r​ ​o​u​r​ ​n​a​t​i​v​e​ ​a​p​p​l​i​c​a​t​i​o​n​ ​"​d​e​f​g​u​a​r​d​"​ ​o​r​ ​a​n​y​ ​o​t​h​e​r​ ​W​i​r​e​G​u​a​r​d​ ​c​l​i​e​n​t​.​ ​I​f​ ​y​o​u​'​r​e​ ​u​n​s​u​r​e​,​ ​w​e​ ​r​e​c​o​m​m​e​n​d​ ​u​s​i​n​g​ ​d​e​f​g​u​a​r​d​ ​f​o​r​ ​s​i​m​p​l​i​c​i​t​y​. */ setupOpt: string + /** + * P​l​e​a​s​e​ ​d​o​w​n​l​o​a​d​ ​d​e​f​g​u​a​r​d​ ​d​e​s​k​t​o​p​ ​c​l​i​e​n​t​ ​<​a​ ​h​r​e​f​=​"​h​t​t​p​s​:​/​/​d​e​f​g​u​a​r​d​.​n​e​t​/​d​o​w​n​l​o​a​d​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​h​e​r​e​<​/​a​>​ ​a​n​d​ ​t​h​e​n​ ​f​o​l​l​o​w​ ​<​a​ ​h​r​e​f​=​"​h​t​t​p​s​:​/​/​d​e​f​g​u​a​r​d​.​g​i​t​b​o​o​k​.​i​o​/​d​e​f​g​u​a​r​d​/​h​e​l​p​/​c​o​n​f​i​g​u​r​i​n​g​-​v​p​n​/​a​d​d​-​n​e​w​-​i​n​s​t​a​n​c​e​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​t​h​i​s​ ​g​u​i​d​e​<​/​a​>​. + */ + client: string } messages: { /** @@ -1222,6 +1235,32 @@ type RootTranslation = { */ deleteApp: string } + warningModals: { + /** + * W​a​r​n​i​n​g + */ + title: string + content: { + /** + * C​h​a​n​g​i​n​g​ ​t​h​e​ ​u​s​e​r​n​a​m​e​ ​h​a​s​ ​a​ ​s​i​g​n​i​f​i​c​a​n​t​ ​i​m​p​a​c​t​ ​o​n​ ​s​e​r​v​i​c​e​s​ ​t​h​e​ ​u​s​e​r​ ​h​a​s​ ​l​o​g​g​e​d​ ​i​n​t​o​ ​u​s​i​n​g​ ​D​e​f​g​u​a​r​d​.​ ​A​f​t​e​r​ ​c​h​a​n​g​i​n​g​ ​i​t​,​ ​t​h​e​ ​u​s​e​r​ ​m​a​y​ ​l​o​s​e​ ​a​c​c​e​s​s​ ​t​o​ ​a​p​p​l​i​c​a​t​i​o​n​s​ ​(​s​i​n​c​e​ ​t​h​e​y​ ​w​i​l​l​ ​n​o​t​ ​r​e​c​o​g​n​i​z​e​ ​t​h​e​m​)​.​ ​A​r​e​ ​y​o​u​ ​s​u​r​e​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​p​r​o​c​e​e​d​? + */ + usernameChange: string + /** + * I​f​ ​y​o​u​ ​a​r​e​ ​u​s​i​n​g​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​C​o​n​n​e​c​t​ ​(​O​I​D​C​)​ ​p​r​o​v​i​d​e​r​s​ ​t​o​ ​a​u​t​h​e​n​t​i​c​a​t​e​ ​u​s​e​r​s​,​ ​c​h​a​n​g​i​n​g​ ​a​ ​u​s​e​r​'​s​ ​e​m​a​i​l​ ​a​d​d​r​e​s​s​ ​m​a​y​ ​h​a​v​e​ ​a​ ​s​i​g​n​i​f​i​c​a​n​t​ ​i​m​p​a​c​t​ ​o​n​ ​t​h​e​i​r​ ​a​b​i​l​i​t​y​ ​t​o​ ​l​o​g​ ​i​n​ ​t​o​ ​D​e​f​g​u​a​r​d​.​ ​A​r​e​ ​y​o​u​ ​s​u​r​e​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​p​r​o​c​e​e​d​? + */ + emailChange: string + } + buttons: { + /** + * P​r​o​c​e​e​d + */ + proceed: string + /** + * C​a​n​c​e​l + */ + cancel: string + } + } fields: { username: { /** @@ -1942,7 +1981,7 @@ type RootTranslation = { support: string } /** - * C​o​p​y​r​i​g​h​t​ ​©​ ​2​0​2​3​ + * C​o​p​y​r​i​g​h​t​ ​©​2​0​2​3​-​2​0​2​4 */ copyright: string version: { @@ -1952,7 +1991,7 @@ type RootTranslation = { */ open: RequiredParams<'version'> /** - * v​ ​{​v​e​r​s​i​o​n​} + * v​{​v​e​r​s​i​o​n​} * @param {string} version */ closed: RequiredParams<'version'> @@ -2096,6 +2135,10 @@ type RootTranslation = { * @param {number} value */ maximumValue: RequiredParams<'value'> + /** + * T​o​o​ ​m​a​n​y​ ​b​a​d​ ​l​o​g​i​n​ ​a​t​t​e​m​p​t​s​.​ ​P​l​e​a​s​e​ ​t​r​y​ ​a​g​a​i​n​ ​i​n​ ​a​ ​f​e​w​ ​m​i​n​u​t​e​s​. + */ + tooManyBadLoginAttempts: string } floatingErrors: { /** @@ -2186,6 +2229,14 @@ type RootTranslation = { * L​D​A​P */ ldap: string + /** + * O​p​e​n​I​D + */ + openid: string + /** + * E​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​s + */ + enterprise: string } messages: { /** @@ -2197,6 +2248,24 @@ type RootTranslation = { */ challengeSuccess: string } + enterpriseOnly: { + /** + * T​h​i​s​ ​f​e​a​t​u​r​e​ ​i​s​ ​a​v​a​i​l​a​b​l​e​ ​o​n​l​y​ ​i​n​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​. + */ + title: string + /** + * Y​o​u​r​ ​c​u​r​r​e​n​t​ ​l​i​c​e​n​s​e​ ​h​a​s​ ​e​x​p​i​r​e​d​. + */ + currentExpired: string + /** + * T​o​ ​l​e​a​r​n​ ​m​o​r​e​,​ ​v​i​s​i​t​ ​o​u​r​ + */ + subtitle: string + /** + * w​e​b​s​i​t​e + */ + website: string + } ldapSettings: { /** * L​D​A​P​ ​S​e​t​t​i​n​g​s @@ -2249,6 +2318,10 @@ type RootTranslation = { */ ldap_group_obj_class: string } + /** + * D​e​l​e​t​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n + */ + 'delete': string } test: { /** @@ -2271,6 +2344,92 @@ type RootTranslation = { } } } + openIdSettings: { + general: { + /** + * E​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​S​e​t​t​i​n​g​s + */ + title: string + /** + * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​c​h​a​n​g​e​ ​g​e​n​e​r​a​l​ ​O​p​e​n​I​D​ ​b​e​h​a​v​i​o​r​ ​i​n​ ​y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​i​n​s​t​a​n​c​e​. + */ + helper: string + createAccount: { + /** + * A​u​t​o​m​a​t​i​c​a​l​l​y​ ​c​r​e​a​t​e​ ​u​s​e​r​ ​a​c​c​o​u​n​t​ ​w​h​e​n​ ​l​o​g​g​i​n​g​ ​i​n​ ​f​o​r​ ​t​h​e​ ​f​i​r​s​t​ ​t​i​m​e​ ​t​h​r​o​u​g​h​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​. + */ + label: string + /** + * I​f​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​D​e​f​g​u​a​r​d​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​c​r​e​a​t​e​s​ ​n​e​w​ ​a​c​c​o​u​n​t​s​ ​f​o​r​ ​u​s​e​r​s​ ​w​h​o​ ​l​o​g​ ​i​n​ ​f​o​r​ ​t​h​e​ ​f​i​r​s​t​ ​t​i​m​e​ ​u​s​i​n​g​ ​a​n​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​.​ ​O​t​h​e​r​w​i​s​e​,​ ​t​h​e​ ​u​s​e​r​ ​a​c​c​o​u​n​t​ ​m​u​s​t​ ​f​i​r​s​t​ ​b​e​ ​c​r​e​a​t​e​d​ ​b​y​ ​a​n​ ​a​d​m​i​n​i​s​t​r​a​t​o​r​. + */ + helper: string + } + } + form: { + /** + * E​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​C​l​i​e​n​t​ ​S​e​t​t​i​n​g​s + */ + title: string + /** + * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​c​o​n​f​i​g​u​r​e​ ​t​h​e​ ​O​p​e​n​I​D​ ​c​l​i​e​n​t​ ​s​e​t​t​i​n​g​s​ ​w​i​t​h​ ​v​a​l​u​e​s​ ​p​r​o​v​i​d​e​d​ ​b​y​ ​y​o​u​r​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​. + */ + helper: string + /** + * C​u​s​t​o​m + */ + custom: string + /** + * D​o​c​u​m​e​n​t​a​t​i​o​n + */ + documentation: string + /** + * D​e​l​e​t​e​ ​p​r​o​v​i​d​e​r + */ + 'delete': string + labels: { + provider: { + /** + * P​r​o​v​i​d​e​r + */ + label: string + /** + * S​e​l​e​c​t​ ​y​o​u​r​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​.​ ​Y​o​u​ ​c​a​n​ ​u​s​e​ ​c​u​s​t​o​m​ ​p​r​o​v​i​d​e​r​ ​a​n​d​ ​f​i​l​l​ ​i​n​ ​t​h​e​ ​b​a​s​e​ ​U​R​L​ ​b​y​ ​y​o​u​r​s​e​l​f​. + */ + helper: string + } + client_id: { + /** + * C​l​i​e​n​t​ ​I​D + */ + label: string + /** + * C​l​i​e​n​t​ ​I​D​ ​p​r​o​v​i​d​e​d​ ​b​y​ ​y​o​u​r​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​. + */ + helper: string + } + client_secret: { + /** + * C​l​i​e​n​t​ ​S​e​c​r​e​t + */ + label: string + /** + * C​l​i​e​n​t​ ​S​e​c​r​e​t​ ​p​r​o​v​i​d​e​d​ ​b​y​ ​y​o​u​r​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​. + */ + helper: string + } + base_url: { + /** + * B​a​s​e​ ​U​R​L + */ + label: string + /** + * B​a​s​e​ ​U​R​L​ ​o​f​ ​y​o​u​r​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​,​ ​e​.​g​.​ ​h​t​t​p​s​:​/​/​a​c​c​o​u​n​t​s​.​g​o​o​g​l​e​.​c​o​m​.​ ​M​a​k​e​ ​s​u​r​e​ ​t​o​ ​c​h​e​c​k​ ​o​u​r​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​ ​f​o​r​ ​m​o​r​e​ ​i​n​f​o​r​m​a​t​i​o​n​ ​a​n​d​ ​e​x​a​m​p​l​e​s​. + */ + helper: string + } + } + } + } modulesVisibility: { /** * M​o​d​u​l​e​s​ ​V​i​s​i​b​i​l​i​t​y @@ -2432,6 +2591,116 @@ type RootTranslation = { */ helper: RequiredParams<'documentationLink'> } + license: { + /** + * E​n​t​e​r​p​r​i​s​e + */ + header: string + helpers: { + enterpriseHeader: { + /** + * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​m​a​n​a​g​e​ ​y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​ ​v​e​r​s​i​o​n​ ​l​i​c​e​n​s​e​. + */ + text: string + /** + * T​o​ ​l​e​a​r​n​ ​m​o​r​e​ ​a​b​o​u​t​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​,​ ​v​i​s​i​t​ ​o​u​r​ ​w​e​b​i​s​t​e​. + */ + link: string + } + licenseKey: { + /** + * E​n​t​e​r​ ​y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​k​e​y​ ​b​e​l​o​w​.​ ​Y​o​u​ ​s​h​o​u​l​d​ ​r​e​c​e​i​v​e​ ​i​t​ ​v​i​a​ ​e​m​a​i​l​ ​a​f​t​e​r​ ​p​u​r​c​h​a​s​i​n​g​ ​t​h​e​ ​l​i​c​e​n​s​e​. + */ + text: string + /** + * Y​o​u​ ​c​a​n​ ​p​u​r​c​h​a​s​e​ ​t​h​e​ ​l​i​c​e​n​s​e​ ​h​e​r​e​. + */ + link: string + } + } + form: { + /** + * L​i​c​e​n​s​e + */ + title: string + fields: { + key: { + /** + * L​i​c​e​n​s​e​ ​k​e​y + */ + label: string + /** + * Y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​l​i​c​e​n​s​e​ ​k​e​y + */ + placeholder: string + } + } + } + licenseInfo: { + /** + * L​i​c​e​n​s​e​ ​i​n​f​o​r​m​a​t​i​o​n + */ + title: string + /** + * N​o​ ​l​i​c​e​n​s​e + */ + noLicense: string + types: { + subscription: { + /** + * S​u​b​s​c​r​i​p​t​i​o​n + */ + label: string + /** + * A​ ​l​i​c​e​n​s​e​ ​t​h​a​t​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​r​e​n​e​w​s​ ​a​t​ ​r​e​g​u​l​a​r​ ​i​n​t​e​r​v​a​l​s + */ + helper: string + } + offline: { + /** + * O​f​f​l​i​n​e + */ + label: string + /** + * T​h​e​ ​l​i​c​e​n​s​e​ ​i​s​ ​v​a​l​i​d​ ​u​n​t​i​l​ ​t​h​e​ ​e​x​p​i​r​y​ ​d​a​t​e​ ​a​n​d​ ​d​o​e​s​ ​n​o​t​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​r​e​n​e​w + */ + helper: string + } + } + fields: { + status: { + /** + * S​t​a​t​u​s + */ + label: string + /** + * A​c​t​i​v​e + */ + active: string + /** + * E​x​p​i​r​e​d + */ + expired: string + /** + * A​ ​s​u​b​s​c​r​i​p​t​i​o​n​ ​l​i​c​e​n​s​e​ ​i​s​ ​c​o​n​s​i​d​e​r​e​d​ ​v​a​l​i​d​ ​f​o​r​ ​s​o​m​e​ ​t​i​m​e​ ​a​f​t​e​r​ ​t​h​e​ ​e​x​p​i​r​a​t​i​o​n​ ​d​a​t​e​ ​t​o​ ​a​c​c​o​u​n​t​ ​f​o​r​ ​p​o​s​s​i​b​l​e​ ​a​u​t​o​m​a​t​i​c​ ​p​a​y​m​e​n​t​ ​d​e​l​a​y​s​. + */ + subscriptionHelper: string + } + type: { + /** + * T​y​p​e + */ + label: string + } + validUntil: { + /** + * V​a​l​i​d​ ​u​n​t​i​l + */ + label: string + } + } + } + } smtp: { form: { /** @@ -2512,6 +2781,10 @@ type RootTranslation = { submit: string } } + /** + * D​e​l​e​t​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n + */ + 'delete': string testForm: { /** * S​e​n​d​ ​t​e​s​t​ ​e​m​a​i​l @@ -2643,6 +2916,48 @@ type RootTranslation = { } } } + enterprise: { + /** + * E​n​t​e​r​p​r​i​s​e​ ​F​e​a​t​u​r​e​s + */ + header: string + /** + * <​p​>​H​e​r​e​ ​y​o​u​ ​c​a​n​ ​c​h​a​n​g​e​ ​e​n​t​e​r​p​r​i​s​e​ ​s​e​t​t​i​n​g​s​.​<​/​p​> + */ + helper: string + fields: { + deviceManagement: { + /** + * D​i​s​a​b​l​e​ ​u​s​e​r​s​'​ ​a​b​i​l​i​t​y​ ​t​o​ ​m​a​n​a​g​e​ ​t​h​e​i​r​ ​d​e​v​i​c​e​s + */ + label: string + /** + * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​o​n​l​y​ ​u​s​e​r​s​ ​i​n​ ​t​h​e​ ​A​d​m​i​n​ ​g​r​o​u​p​ ​c​a​n​ ​m​a​n​a​g​e​ ​d​e​v​i​c​e​s​ ​i​n​ ​u​s​e​r​ ​p​r​o​f​i​l​e​ ​(​i​t​'​s​ ​d​i​s​a​b​l​e​d​ ​f​o​r​ ​a​l​l​ ​o​t​h​e​r​ ​u​s​e​r​s​) + */ + helper: string + } + disableAllTraffic: { + /** + * D​i​s​a​b​l​e​ ​t​h​e​ ​o​p​t​i​o​n​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​V​P​N + */ + label: string + /** + * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​u​s​e​r​s​ ​w​i​l​l​ ​n​o​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​t​h​e​ ​V​P​N​ ​u​s​i​n​g​ ​t​h​e​ ​d​e​f​g​u​a​r​d​ ​c​l​i​e​n​t​. + */ + helper: string + } + manualConfig: { + /** + * D​i​s​a​b​l​e​ ​u​s​e​r​s​'​ ​a​b​i​l​i​t​y​ ​t​o​ ​m​a​n​u​a​l​l​y​ ​c​o​n​f​i​g​u​r​e​ ​W​i​r​e​G​u​a​r​d​ ​c​l​i​e​n​t + */ + label: string + /** + * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​u​s​e​r​s​ ​w​o​n​'​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​v​i​e​w​ ​o​r​ ​d​o​w​n​l​o​a​d​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​o​r​ ​t​h​e​ ​m​a​n​u​a​l​ ​W​i​r​e​G​u​a​r​d​ ​c​l​i​e​n​t​ ​s​e​t​u​p​.​ ​O​n​l​y​ ​t​h​e​ ​D​e​f​g​u​a​r​d​ ​d​e​s​k​t​o​p​ ​c​l​i​e​n​t​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​a​v​a​i​l​a​b​l​e​. + */ + helper: string + } + } + } } openidOverview: { /** @@ -3396,15 +3711,39 @@ type RootTranslation = { } } gatewaySetup: { - /** - * G​a​t​e​w​a​y​ ​s​e​r​v​e​r​ ​s​e​t​u​p - */ - header: string + header: { + /** + * G​a​t​e​w​a​y​ ​s​e​r​v​e​r​ ​s​e​t​u​p + */ + main: string + /** + * D​o​c​k​e​r​ ​B​a​s​e​d​ ​G​a​t​e​w​a​y​ ​S​e​t​u​p + */ + dockerBasedGatewaySetup: string + /** + * F​r​o​m​ ​P​a​c​k​a​g​e + */ + fromPackage: string + /** + * O​n​e​ ​L​i​n​e​ ​I​n​s​t​a​l​l + */ + oneLineInstall: string + } card: { /** * D​o​c​k​e​r​ ​b​a​s​e​d​ ​g​a​t​e​w​a​y​ ​s​e​t​u​p */ title: string + /** + * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​T​o​k​e​n + */ + authToken: string + } + button: { + /** + * A​v​a​i​l​a​b​l​e​ ​P​a​c​k​a​g​e​s + */ + availablePackages: string } controls: { /** @@ -3414,35 +3753,52 @@ type RootTranslation = { } messages: { /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​D​e​f​g​u​a​r​d​ ​r​e​q​u​i​r​e​s​ ​t​o​ ​d​e​p​l​o​y​ ​a​ ​g​a​t​e​w​a​y​ ​n​o​d​e​ ​t​o​ ​c​o​n​t​r​o​l​ ​w​i​r​e​g​u​a​r​d​ ​V​P​N​ ​o​n​ ​t​h​e​ ​v​p​n​ ​s​e​r​v​e​r​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​M​o​r​e​ ​d​e​t​a​i​l​s​ ​c​a​n​ ​b​e​ ​f​o​u​n​d​ ​i​n​ ​t​h​e​ ​<​a​ ​h​r​e​f​=​"​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​d​o​c​u​m​e​n​t​a​t​i​o​n​<​/​a​>​.​ + * D​e​f​g​u​a​r​d​ ​r​e​q​u​i​r​e​s​ ​t​o​ ​d​e​p​l​o​y​ ​a​ ​g​a​t​e​w​a​y​ ​n​o​d​e​ ​t​o​ ​c​o​n​t​r​o​l​ ​w​i​r​e​g​u​a​r​d​ ​V​P​N​ ​o​n​ ​t​h​e​ ​v​p​n​ ​s​e​r​v​e​r​.​ + ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​M​o​r​e​ ​d​e​t​a​i​l​s​ ​c​a​n​ ​b​e​ ​f​o​u​n​d​ ​i​n​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​.​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​T​h​e​r​e​ ​a​r​e​ ​s​e​v​e​r​a​l​ ​w​a​y​s​ ​t​o​ ​d​e​p​l​o​y​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​s​e​r​v​e​r​,​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​b​e​l​o​w​ ​i​s​ ​a​ ​D​o​c​k​e​r​ ​b​a​s​e​d​ ​e​x​a​m​p​l​e​,​ ​f​o​r​ ​o​t​h​e​r​ ​e​x​a​m​p​l​e​s​ ​p​l​e​a​s​e​ ​v​i​s​i​t​ ​<​a​ ​h​r​e​f​=​"​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​d​o​c​u​m​e​n​t​a​t​i​o​n​<​/​a​>​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​> + ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​b​e​l​o​w​ ​i​s​ ​a​ ​D​o​c​k​e​r​ ​b​a​s​e​d​ ​e​x​a​m​p​l​e​,​ ​f​o​r​ ​o​t​h​e​r​ ​e​x​a​m​p​l​e​s​ ​p​l​e​a​s​e​ ​v​i​s​i​t​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​. * @param {string} setupGatewayDocs */ runCommand: RequiredParams<'setupGatewayDocs' | 'setupGatewayDocs'> /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​P​l​e​a​s​e​ ​c​r​e​a​t​e​ ​t​h​e​ ​n​e​t​w​o​r​k​ ​b​e​f​o​r​e​ ​r​u​n​n​i​n​g​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​p​r​o​c​e​s​s​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​> + * P​l​e​a​s​e​ ​c​r​e​a​t​e​ ​t​h​e​ ​n​e​t​w​o​r​k​ ​b​e​f​o​r​e​ ​r​u​n​n​i​n​g​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​p​r​o​c​e​s​s​. */ createNetwork: string /** - * <​p​>​N​o​ ​c​o​n​n​e​c​t​i​o​n​ ​e​s​t​a​b​l​i​s​h​e​d​,​ ​p​l​e​a​s​e​ ​r​u​n​ ​p​r​o​v​i​d​e​d​ ​c​o​m​m​a​n​d​.​<​/​p​> + * N​o​ ​c​o​n​n​e​c​t​i​o​n​ ​e​s​t​a​b​l​i​s​h​e​d​,​ ​p​l​e​a​s​e​ ​r​u​n​ ​p​r​o​v​i​d​e​d​ ​c​o​m​m​a​n​d​. */ noConnection: string /** - * <​p​>​G​a​t​e​w​a​y​ ​c​o​n​n​e​c​t​e​d​.​<​/​p​> + * G​a​t​e​w​a​y​ ​c​o​n​n​e​c​t​e​d​. */ connected: string /** * F​a​i​l​e​d​ ​t​o​ ​g​e​t​ ​g​a​t​e​w​a​y​ ​s​t​a​t​u​s */ statusError: string + /** + * I​f​ ​y​o​u​ ​a​r​e​ ​d​o​i​n​g​ ​o​n​e​ ​l​i​n​e​ ​i​n​s​t​a​l​l​:​ ​h​t​t​p​s​:​/​/​d​e​f​g​u​a​r​d​.​g​i​t​b​o​o​k​.​i​o​/​d​e​f​g​u​a​r​d​/​a​d​m​i​n​-​a​n​d​-​f​e​a​t​u​r​e​s​/​s​e​t​t​i​n​g​-​u​p​-​y​o​u​r​-​i​n​s​t​a​n​c​e​/​o​n​e​-​l​i​n​e​-​i​n​s​t​a​l​l​ + ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​y​o​u​ ​d​o​n​'​t​ ​n​e​e​d​ ​t​o​ ​d​o​ ​a​n​y​t​h​i​n​g​. + */ + oneLineInstall: string + /** + * I​n​s​t​a​l​l​ ​t​h​e​ ​p​a​c​k​a​g​e​ ​a​v​a​i​l​a​b​l​e​ ​a​t​ ​h​t​t​p​s​:​/​/​g​i​t​h​u​b​.​c​o​m​/​D​e​f​G​u​a​r​d​/​g​a​t​e​w​a​y​/​r​e​l​e​a​s​e​s​/​l​a​t​e​s​t​ ​a​n​d​ ​c​o​n​f​i​g​u​r​e​ ​`​/​e​t​c​/​d​e​f​g​u​a​r​d​/​g​a​t​e​w​a​y​.​t​o​m​l​`​ + ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​a​c​c​o​r​d​i​n​g​ ​t​o​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​. + * @param {string} setupGatewayDocs + */ + fromPackage: RequiredParams<'setupGatewayDocs'> + /** + * T​o​k​e​n​ ​b​e​l​o​w​ ​i​s​ ​r​e​q​u​i​r​e​d​ ​t​o​ ​a​u​t​h​e​n​t​i​c​a​t​e​ ​a​n​d​ ​c​o​n​f​i​g​u​r​e​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​n​o​d​e​.​ ​E​n​s​u​r​e​ ​y​o​u​ ​k​e​e​p​ ​t​h​i​s​ ​t​o​k​e​n​ ​s​e​c​u​r​e​ ​a​n​d​ ​f​o​l​l​o​w​ ​t​h​e​ ​d​e​p​l​o​y​m​e​n​t​ ​i​n​s​t​r​u​c​t​i​o​n​s​ + ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​p​r​o​v​i​d​e​d​ ​i​n​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​ ​t​o​ ​s​u​c​c​e​s​s​f​u​l​l​y​ ​s​e​t​ ​u​p​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​s​e​r​v​e​r​.​ + ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​F​o​r​ ​m​o​r​e​ ​d​e​t​a​i​l​s​ ​a​n​d​ ​e​x​a​c​t​ ​s​t​e​p​s​,​ ​p​l​e​a​s​e​ ​r​e​f​e​r​ ​t​o​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​. + * @param {string} setupGatewayDocs + */ + authToken: RequiredParams<'setupGatewayDocs' | 'setupGatewayDocs'> + /** + * B​e​l​o​w​ ​i​s​ ​a​ ​D​o​c​k​e​r​ ​b​a​s​e​d​ ​e​x​a​m​p​l​e​.​ ​F​o​r​ ​m​o​r​e​ ​d​e​t​a​i​l​s​ ​a​n​d​ ​e​x​a​c​t​ ​s​t​e​p​s​,​ ​p​l​e​a​s​e​ ​r​e​f​e​r​ ​t​o​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​. + * @param {string} setupGatewayDocs + */ + dockerBasedGatewaySetup: RequiredParams<'setupGatewayDocs'> } } loginPage: { @@ -3450,6 +3806,16 @@ type RootTranslation = { * E​n​t​e​r​ ​y​o​u​r​ ​c​r​e​d​e​n​t​i​a​l​s */ pageTitle: string + callback: { + /** + * G​o​ ​b​a​c​k​ ​t​o​ ​l​o​g​i​n + */ + 'return': string + /** + * A​n​ ​e​r​r​o​r​ ​o​c​c​u​r​r​e​d​ ​d​u​r​i​n​g​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​l​o​g​i​n + */ + error: string + } mfa: { /** * T​w​o​-​f​a​c​t​o​r​ ​a​u​t​h​e​n​t​i​c​a​t​i​o​n @@ -3789,7 +4155,7 @@ type RootTranslation = { } } /** - * E​n​r​o​l​l​m​e​n​t​ ​i​s​ ​p​r​o​c​e​s​s​ ​b​y​ ​w​h​i​c​h​ ​t​h​e​ ​n​e​w​ ​e​m​p​l​o​y​e​e​ ​w​i​l​l​ ​b​e​ ​a​b​l​e​ ​t​o​ ​c​o​n​f​i​r​m​ ​t​h​e​i​r​ ​n​e​w​ ​a​c​c​o​u​n​t​,​ ​c​r​e​a​t​e​ ​a​ ​p​a​s​s​w​o​r​d​ ​a​n​d​ ​c​o​n​f​i​g​u​r​a​t​e​ ​V​P​N​ ​d​e​v​i​c​e​.​ ​I​n​ ​t​h​i​s​ ​p​a​n​e​l​ ​y​o​u​ ​c​a​n​ ​c​u​s​t​o​m​ ​m​e​s​s​a​g​e​s​ ​f​o​r​ ​i​t​. + * E​n​r​o​l​l​m​e​n​t​ ​i​s​ ​a​ ​p​r​o​c​e​s​s​ ​b​y​ ​w​h​i​c​h​ ​a​ ​n​e​w​ ​e​m​p​l​o​y​e​e​ ​w​i​l​l​ ​b​e​ ​a​b​l​e​ ​t​o​ ​a​c​t​i​v​a​t​e​ ​t​h​e​i​r​ ​n​e​w​ ​a​c​c​o​u​n​t​,​ ​c​r​e​a​t​e​ ​a​ ​p​a​s​s​w​o​r​d​ ​a​n​d​ ​c​o​n​f​i​g​u​r​e​ ​a​ ​V​P​N​ ​d​e​v​i​c​e​.​ ​Y​o​u​ ​c​a​n​ ​c​u​s​t​o​m​i​z​e​ ​i​t​ ​h​e​r​e​. */ messageBox: string settings: { @@ -4025,6 +4391,10 @@ export type TranslationFunctions = { * Context is not secure. */ insecureContext: () => LocalizedString + /** + * Details: + */ + details: () => LocalizedString clipboard: { /** * Clipboard is not accessible. @@ -4771,9 +5141,13 @@ export type TranslationFunctions = { } enableEnrollment: { /** - * Use enrollment process + * Use user self-enrollment process */ label: () => LocalizedString + /** + * more information here + */ + link: () => LocalizedString } } } @@ -4921,6 +5295,10 @@ export type TranslationFunctions = { * You can add a device using this wizard. Opt for our native application "defguard" or any other WireGuard client. If you're unsure, we recommend using defguard for simplicity. */ setupOpt: () => LocalizedString + /** + * Please download defguard desktop client here and then follow this guide. + */ + client: () => LocalizedString } messages: { /** @@ -5116,6 +5494,32 @@ export type TranslationFunctions = { */ deleteApp: () => LocalizedString } + warningModals: { + /** + * Warning + */ + title: () => LocalizedString + content: { + /** + * Changing the username has a significant impact on services the user has logged into using Defguard. After changing it, the user may lose access to applications (since they will not recognize them). Are you sure you want to proceed? + */ + usernameChange: () => LocalizedString + /** + * If you are using external OpenID Connect (OIDC) providers to authenticate users, changing a user's email address may have a significant impact on their ability to log in to Defguard. Are you sure you want to proceed? + */ + emailChange: () => LocalizedString + } + buttons: { + /** + * Proceed + */ + proceed: () => LocalizedString + /** + * Cancel + */ + cancel: () => LocalizedString + } + } fields: { username: { /** @@ -5834,7 +6238,7 @@ export type TranslationFunctions = { support: () => LocalizedString } /** - * Copyright © 2023 + * Copyright ©2023-2024 */ copyright: () => LocalizedString version: { @@ -5843,7 +6247,7 @@ export type TranslationFunctions = { */ open: (arg: { version: string }) => LocalizedString /** - * v {version} + * v{version} */ closed: (arg: { version: string }) => LocalizedString } @@ -5984,6 +6388,10 @@ export type TranslationFunctions = { * Maximum value of {value} exceeded. */ maximumValue: (arg: { value: number }) => LocalizedString + /** + * Too many bad login attempts. Please try again in a few minutes. + */ + tooManyBadLoginAttempts: () => LocalizedString } floatingErrors: { /** @@ -6074,6 +6482,14 @@ export type TranslationFunctions = { * LDAP */ ldap: () => LocalizedString + /** + * OpenID + */ + openid: () => LocalizedString + /** + * Enterprise features + */ + enterprise: () => LocalizedString } messages: { /** @@ -6085,6 +6501,24 @@ export type TranslationFunctions = { */ challengeSuccess: () => LocalizedString } + enterpriseOnly: { + /** + * This feature is available only in Defguard Enterprise. + */ + title: () => LocalizedString + /** + * Your current license has expired. + */ + currentExpired: () => LocalizedString + /** + * To learn more, visit our + */ + subtitle: () => LocalizedString + /** + * website + */ + website: () => LocalizedString + } ldapSettings: { /** * LDAP Settings @@ -6137,6 +6571,10 @@ export type TranslationFunctions = { */ ldap_group_obj_class: () => LocalizedString } + /** + * Delete configuration + */ + 'delete': () => LocalizedString } test: { /** @@ -6159,6 +6597,92 @@ export type TranslationFunctions = { } } } + openIdSettings: { + general: { + /** + * External OpenID Settings + */ + title: () => LocalizedString + /** + * Here you can change general OpenID behavior in your Defguard instance. + */ + helper: () => LocalizedString + createAccount: { + /** + * Automatically create user account when logging in for the first time through external OpenID. + */ + label: () => LocalizedString + /** + * If this option is enabled, Defguard automatically creates new accounts for users who log in for the first time using an external OpenID provider. Otherwise, the user account must first be created by an administrator. + */ + helper: () => LocalizedString + } + } + form: { + /** + * External OpenID Client Settings + */ + title: () => LocalizedString + /** + * Here you can configure the OpenID client settings with values provided by your external OpenID provider. + */ + helper: () => LocalizedString + /** + * Custom + */ + custom: () => LocalizedString + /** + * Documentation + */ + documentation: () => LocalizedString + /** + * Delete provider + */ + 'delete': () => LocalizedString + labels: { + provider: { + /** + * Provider + */ + label: () => LocalizedString + /** + * Select your OpenID provider. You can use custom provider and fill in the base URL by yourself. + */ + helper: () => LocalizedString + } + client_id: { + /** + * Client ID + */ + label: () => LocalizedString + /** + * Client ID provided by your OpenID provider. + */ + helper: () => LocalizedString + } + client_secret: { + /** + * Client Secret + */ + label: () => LocalizedString + /** + * Client Secret provided by your OpenID provider. + */ + helper: () => LocalizedString + } + base_url: { + /** + * Base URL + */ + label: () => LocalizedString + /** + * Base URL of your OpenID provider, e.g. https://accounts.google.com. Make sure to check our documentation for more information and examples. + */ + helper: () => LocalizedString + } + } + } + } modulesVisibility: { /** * Modules Visibility @@ -6317,6 +6841,116 @@ export type TranslationFunctions = { */ helper: (arg: { documentationLink: string }) => LocalizedString } + license: { + /** + * Enterprise + */ + header: () => LocalizedString + helpers: { + enterpriseHeader: { + /** + * Here you can manage your Defguard Enterprise version license. + */ + text: () => LocalizedString + /** + * To learn more about Defguard Enterprise, visit our webiste. + */ + link: () => LocalizedString + } + licenseKey: { + /** + * Enter your Defguard Enterprise license key below. You should receive it via email after purchasing the license. + */ + text: () => LocalizedString + /** + * You can purchase the license here. + */ + link: () => LocalizedString + } + } + form: { + /** + * License + */ + title: () => LocalizedString + fields: { + key: { + /** + * License key + */ + label: () => LocalizedString + /** + * Your Defguard license key + */ + placeholder: () => LocalizedString + } + } + } + licenseInfo: { + /** + * License information + */ + title: () => LocalizedString + /** + * No license + */ + noLicense: () => LocalizedString + types: { + subscription: { + /** + * Subscription + */ + label: () => LocalizedString + /** + * A license that automatically renews at regular intervals + */ + helper: () => LocalizedString + } + offline: { + /** + * Offline + */ + label: () => LocalizedString + /** + * The license is valid until the expiry date and does not automatically renew + */ + helper: () => LocalizedString + } + } + fields: { + status: { + /** + * Status + */ + label: () => LocalizedString + /** + * Active + */ + active: () => LocalizedString + /** + * Expired + */ + expired: () => LocalizedString + /** + * A subscription license is considered valid for some time after the expiration date to account for possible automatic payment delays. + */ + subscriptionHelper: () => LocalizedString + } + type: { + /** + * Type + */ + label: () => LocalizedString + } + validUntil: { + /** + * Valid until + */ + label: () => LocalizedString + } + } + } + } smtp: { form: { /** @@ -6397,6 +7031,10 @@ export type TranslationFunctions = { submit: () => LocalizedString } } + /** + * Delete configuration + */ + 'delete': () => LocalizedString testForm: { /** * Send test email @@ -6528,6 +7166,48 @@ export type TranslationFunctions = { } } } + enterprise: { + /** + * Enterprise Features + */ + header: () => LocalizedString + /** + *

Here you can change enterprise settings.

+ */ + helper: () => LocalizedString + fields: { + deviceManagement: { + /** + * Disable users' ability to manage their devices + */ + label: () => LocalizedString + /** + * When this option is enabled, only users in the Admin group can manage devices in user profile (it's disabled for all other users) + */ + helper: () => LocalizedString + } + disableAllTraffic: { + /** + * Disable the option to route all traffic through VPN + */ + label: () => LocalizedString + /** + * When this option is enabled, users will not be able to route all traffic through the VPN using the defguard client. + */ + helper: () => LocalizedString + } + manualConfig: { + /** + * Disable users' ability to manually configure WireGuard client + */ + label: () => LocalizedString + /** + * When this option is enabled, users won't be able to view or download configuration for the manual WireGuard client setup. Only the Defguard desktop client configuration will be available. + */ + helper: () => LocalizedString + } + } + } } openidOverview: { /** @@ -7274,15 +7954,39 @@ export type TranslationFunctions = { } } gatewaySetup: { - /** - * Gateway server setup - */ - header: () => LocalizedString + header: { + /** + * Gateway server setup + */ + main: () => LocalizedString + /** + * Docker Based Gateway Setup + */ + dockerBasedGatewaySetup: () => LocalizedString + /** + * From Package + */ + fromPackage: () => LocalizedString + /** + * One Line Install + */ + oneLineInstall: () => LocalizedString + } card: { /** * Docker based gateway setup */ title: () => LocalizedString + /** + * Authentication Token + */ + authToken: () => LocalizedString + } + button: { + /** + * Available Packages + */ + availablePackages: () => LocalizedString } controls: { /** @@ -7292,34 +7996,48 @@ export type TranslationFunctions = { } messages: { /** - * -

- Defguard requires to deploy a gateway node to control wireguard VPN on the vpn server. - More details can be found in the documentation. + * Defguard requires to deploy a gateway node to control wireguard VPN on the vpn server. + More details can be found in the [documentation]({setupGatewayDocs}). There are several ways to deploy the gateway server, - below is a Docker based example, for other examples please visit documentation. -

+ below is a Docker based example, for other examples please visit [documentation]({setupGatewayDocs}). */ runCommand: (arg: { setupGatewayDocs: string }) => LocalizedString /** - * -

- Please create the network before running the gateway process. -

+ * Please create the network before running the gateway process. */ createNetwork: () => LocalizedString /** - *

No connection established, please run provided command.

+ * No connection established, please run provided command. */ noConnection: () => LocalizedString /** - *

Gateway connected.

+ * Gateway connected. */ connected: () => LocalizedString /** * Failed to get gateway status */ statusError: () => LocalizedString + /** + * If you are doing one line install: https://defguard.gitbook.io/defguard/admin-and-features/setting-up-your-instance/one-line-install + you don't need to do anything. + */ + oneLineInstall: () => LocalizedString + /** + * Install the package available at https://github.com/DefGuard/gateway/releases/latest and configure `/etc/defguard/gateway.toml` + according to the [documentation]({setupGatewayDocs}). + */ + fromPackage: (arg: { setupGatewayDocs: string }) => LocalizedString + /** + * Token below is required to authenticate and configure the gateway node. Ensure you keep this token secure and follow the deployment instructions + provided in the [documentation]({setupGatewayDocs}) to successfully set up the gateway server. + For more details and exact steps, please refer to the [documentation]({setupGatewayDocs}). + */ + authToken: (arg: { setupGatewayDocs: string }) => LocalizedString + /** + * Below is a Docker based example. For more details and exact steps, please refer to the [documentation]({setupGatewayDocs}). + */ + dockerBasedGatewaySetup: (arg: { setupGatewayDocs: string }) => LocalizedString } } loginPage: { @@ -7327,6 +8045,16 @@ export type TranslationFunctions = { * Enter your credentials */ pageTitle: () => LocalizedString + callback: { + /** + * Go back to login + */ + 'return': () => LocalizedString + /** + * An error occurred during external OpenID login + */ + error: () => LocalizedString + } mfa: { /** * Two-factor authentication @@ -7666,7 +8394,7 @@ export type TranslationFunctions = { } } /** - * Enrollment is process by which the new employee will be able to confirm their new account, create a password and configurate VPN device. In this panel you can custom messages for it. + * Enrollment is a process by which a new employee will be able to activate their new account, create a password and configure a VPN device. You can customize it here. */ messageBox: () => LocalizedString settings: { diff --git a/web/src/i18n/i18n-util.async.ts b/web/src/i18n/i18n-util.async.ts index 357fc24589..25819ad3e9 100644 --- a/web/src/i18n/i18n-util.async.ts +++ b/web/src/i18n/i18n-util.async.ts @@ -7,6 +7,7 @@ import { loadedFormatters, loadedLocales, locales } from './i18n-util' const localeTranslationLoaders = { en: () => import('./en'), + ko: () => import('./ko'), pl: () => import('./pl'), } diff --git a/web/src/i18n/i18n-util.sync.ts b/web/src/i18n/i18n-util.sync.ts index fff46da0d4..e3a5b51c64 100644 --- a/web/src/i18n/i18n-util.sync.ts +++ b/web/src/i18n/i18n-util.sync.ts @@ -6,10 +6,12 @@ import type { Locales, Translations } from './i18n-types' import { loadedFormatters, loadedLocales, locales } from './i18n-util' import en from './en' +import ko from './ko' import pl from './pl' const localeTranslations = { en, + ko, pl, } diff --git a/web/src/i18n/i18n-util.ts b/web/src/i18n/i18n-util.ts index 8e7ecdc06c..65ae070914 100644 --- a/web/src/i18n/i18n-util.ts +++ b/web/src/i18n/i18n-util.ts @@ -12,6 +12,7 @@ export const baseLocale: Locales = 'en' export const locales: Locales[] = [ 'en', + 'ko', 'pl' ] diff --git a/web/src/i18n/ko/index.ts b/web/src/i18n/ko/index.ts new file mode 100644 index 0000000000..eeacc0cc06 --- /dev/null +++ b/web/src/i18n/ko/index.ts @@ -0,0 +1,1812 @@ +/* eslint-disable max-len */ +import type { BaseTranslation } from '../i18n-types'; + +const ko: BaseTranslation = { + common: { + conditions: { + or: '또는', + and: '그리고', + equal: '같음', + }, + controls: { + next: '다음', + back: '뒤로', + cancel: '취소', + confirm: '확인', + submit: '제출', + close: '닫기', + select: '선택', + finish: '완료', + saveChanges: '변경 사항 저장', + save: '저장', + RestoreDefault: '기본값 복원', + delete: '삭제', + rename: '이름 변경', + copy: '복사', + edit: '편집', + }, + key: '키', + name: '이름', + }, + messages: { + error: '오류가 발생했습니다.', + success: '작업이 성공했습니다', + errorVersion: '애플리케이션 버전을 가져오지 못했습니다.', + insecureContext: '컨텍스트가 안전하지 않습니다.', + details: '상세내용:', + clipboard: { + error: '클립보드에 액세스할 수 없습니다.', + success: '클립보드에 복사되었습니다.', + }, + }, + modals: { + addGroup: { + title: '그룹 추가', + selectAll: '모든 사용자 선택', + groupName: '그룹 이름', + searchPlaceholder: '필터/검색', + submit: '그룹 생성', + }, + editGroup: { + title: '그룹 편집', + selectAll: '모든 사용자 선택', + groupName: '그룹 이름', + searchPlaceholder: '필터/검색', + submit: '그룹 업데이트', + }, + deleteGroup: { + title: '{name:string} 그룹 삭제', + subTitle: '이 작업은 이 그룹을 영구적으로 삭제합니다.', + locationListHeader: '이 그룹은 현재 다음 VPN 위치에 할당되어 있습니다:', + locationListFooter: `이것이 주어진 위치에 허용된 유일한 그룹인 경우, 해당 위치는 모든 사용자가 액세스할 수 있게 됩니다.`, + submit: '그룹 삭제', + cancel: '취소', + }, + deviceConfig: { + title: '장치 VPN 구성', + }, + changePasswordSelf: { + title: '비밀번호 변경', + messages: { + success: '비밀번호가 변경되었습니다', + error: '비밀번호 변경에 실패했습니다', + }, + form: { + labels: { + newPassword: '새 비밀번호', + oldPassword: '현재 비밀번호', + repeat: '새 비밀번호 확인', + }, + }, + controls: { + submit: '비밀번호 변경', + cancel: '취소', + }, + }, + startEnrollment: { + title: '등록 시작', + desktopTitle: '데스크톱 활성화', + messages: { + success: '사용자 등록이 시작되었습니다', + successDesktop: '데스크톱 구성이 시작되었습니다', + error: '사용자 등록을 시작하지 못했습니다', + errorDesktop: '데스크톱 활성화를 시작하지 못했습니다', + }, + form: { + email: { + label: '이메일', + }, + mode: { + options: { + email: '이메일로 토큰 보내기', + manual: '직접 토큰 전달', + }, + }, + submit: '등록 시작', + submitDesktop: '데스크톱 활성화', + smtpDisabled: + '이메일로 토큰을 보내려면 SMTP를 구성하십시오. 설정 -> SMTP로 이동하십시오.', + }, + tokenCard: { + title: '활성화 토큰', + }, + urlCard: { + title: 'Defguard 인스턴스 URL', + }, + }, + deleteNetwork: { + title: '{name:string} 위치 삭제', + subTitle: '이 작업은 이 위치를 영구적으로 삭제합니다.', + submit: '위치 삭제', + cancel: '취소', + }, + changeWebhook: { + messages: { + success: 'Webhook이 변경되었습니다.', + }, + }, + manageWebAuthNKeys: { + title: '보안 키', + messages: { + deleted: 'WebAuthN 키가 삭제되었습니다.', + duplicateKeyError: '키가 이미 등록되어 있습니다', + }, + infoMessage: ` +

+ 보안 키는 인증 코드 대신 2단계 인증으로 사용될 수 있습니다. + + 보안 키 구성에 대해 자세히 알아보세요. +

+`, + form: { + messages: { + success: '보안 키가 추가되었습니다.', + }, + fields: { + name: { + label: '새 키 이름', + }, + }, + controls: { + submit: '새 키 추가', + }, + }, + }, + recoveryCodes: { + title: '복구 코드', + submit: '코드를 저장했습니다', + messages: { + copied: '코드가 복사되었습니다.', + }, + infoMessage: ` +

+ 복구 코드는 비밀번호와 동일한 수준의 주의를 기울여 취급하십시오! + + Lastpass, bitwarden 또는 Keeper와 같은 비밀번호 관리자를 사용하여 저장하는 것을 권장합니다. +

+`, + }, + registerTOTP: { + title: 'Authenticator 앱 설정', + infoMessage: ` +

+ MFA를 설정하려면, 이 QR 코드를 인증 앱으로 스캔한 다음, + 아래 필드에 코드를 입력하세요: +

+`, + messages: { + totpCopied: 'TOTP 경로가 복사되었습니다.', + success: 'TOTP가 활성화되었습니다', + }, + copyPath: 'TOTP 경로 복사', + form: { + fields: { + code: { + label: 'Authenticator 코드', + error: '코드가 유효하지 않습니다', + }, + }, + controls: { + submit: '코드 확인', + }, + }, + }, + registerEmailMFA: { + title: '이메일 MFA 설정', + infoMessage: ` +

+ MFA를 설정하려면 계정 이메일: {email: string}로 전송된 코드를 입력하세요 +

+`, + messages: { + success: '이메일 MFA가 활성화되었습니다', + resend: '인증 코드가 재전송되었습니다', + }, + form: { + fields: { + code: { + label: '이메일 코드', + error: '코드가 유효하지 않습니다', + }, + }, + controls: { + submit: '코드 확인', + resend: '이메일 재전송', + }, + }, + }, + editDevice: { + title: '장치 편집', + messages: { + success: '장치가 업데이트되었습니다.', + }, + form: { + fields: { + name: { + label: '장치 이름', + }, + publicKey: { + label: '장치 공개 키 (WireGuard)', + }, + }, + controls: { + submit: '장치 편집', + }, + }, + }, + deleteDevice: { + title: '장치 삭제', + message: '{deviceName} 장치를 삭제하시겠습니까?', + submit: '장치 삭제', + messages: { + success: '장치가 삭제되었습니다.', + }, + }, + addWallet: { + title: '지갑 추가', + infoBox: 'ETH 지갑을 추가하려면 메시지에 서명해야 합니다.', + form: { + fields: { + name: { + placeholder: '지갑 이름', + label: '이름', + }, + address: { + placeholder: '지갑 주소', + label: '주소', + }, + }, + controls: { + submit: '지갑 추가', + }, + }, + }, + keyDetails: { + title: 'YubiKey 세부 정보', + downloadAll: '모든 키 다운로드', + }, + deleteUser: { + title: '계정 삭제', + controls: { + submit: '계정 삭제', + }, + message: '{username: string} 계정을 영구적으로 삭제하시겠습니까?', + messages: { + success: '{username: string}이(가) 삭제되었습니다.', + }, + }, + disableUser: { + title: '계정 비활성화', + controls: { + submit: '계정 비활성화', + }, + message: '{username: string} 계정을 비활성화하시겠습니까?', + messages: { + success: '{username: string}이(가) 비활성화되었습니다.', + }, + }, + enableUser: { + title: '계정 활성화', + controls: { + submit: '계정 활성화', + }, + message: '{username: string} 계정을 활성화하시겠습니까?', + messages: { + success: '{username: string}이(가) 활성화되었습니다.', + }, + }, + deleteProvisioner: { + title: '프로비저너 삭제', + controls: { + submit: '프로비저너 삭제', + }, + message: '{id: string} 프로비저너를 삭제하시겠습니까?', + messages: { + success: '{provisioner: string}이(가) 삭제되었습니다.', + }, + }, + changeUserPassword: { + messages: { + success: '비밀번호가 변경되었습니다.', + }, + title: '사용자 비밀번호 변경', + form: { + controls: { + submit: '새 비밀번호 저장', + }, + fields: { + newPassword: { + label: '새 비밀번호', + }, + confirmPassword: { + label: '비밀번호 다시 입력', + }, + }, + }, + }, + provisionKeys: { + title: 'Yubikey 프로비저닝:', + warning: '이 작업은 yubikey의 openpgp 애플리케이션을 삭제하고 재구성합니다.', + infoBox: `선택한 프로비저너에는 프로비저닝할 깨끗한 YubiKey가 + 연결되어 있어야 합니다. 사용된 YubiKey를 청소하려면 프로비저닝하기 전에 + gpg --card-edit 를 실행하십시오.`, + selectionLabel: '다음 프로비저너 중 하나를 선택하여 YubiKey를 프로비저닝하십시오:', + noData: { + workers: '작업자를 찾을 수 없습니다. 대기 중...', + }, + controls: { + submit: 'YubiKey 프로비저닝', + }, + messages: { + success: '키가 프로비저닝되었습니다', + errorStatus: '작업자 상태를 가져오는 중 오류가 발생했습니다.', + }, + }, + addUser: { + title: '새 사용자 추가', + messages: { + userAdded: '사용자가 추가되었습니다', + }, + form: { + submit: '사용자 추가', + fields: { + username: { + placeholder: '로그인', + label: '로그인', + }, + password: { + placeholder: '비밀번호', + label: '비밀번호', + }, + email: { + placeholder: '사용자 이메일', + label: '사용자 이메일', + }, + firstName: { + placeholder: '이름', + label: '이름', + }, + lastName: { + placeholder: '성', + label: '성', + }, + phone: { + placeholder: '전화번호', + label: '전화번호', + }, + enableEnrollment: { + label: '등록 프로세스 사용', + link: '자세한 정보는 여기를 참고하세요', + }, + }, + }, + }, + webhookModal: { + title: { + addWebhook: '웹훅 추가.', + editWebhook: '웹훅 편집', + }, + messages: { + clientIdCopy: '클라이언트 ID가 복사되었습니다.', + clientSecretCopy: '클라이언트 암호가 복사되었습니다.', + }, + form: { + triggers: '트리거 이벤트:', + messages: { + successAdd: '웹훅이 생성되었습니다.', + successModify: '웹훅이 수정되었습니다.', + }, + error: { + urlRequired: 'URL이 필요합니다.', + validUrl: '유효한 URL이어야 합니다.', + scopeValidation: '최소 하나의 트리거가 있어야 합니다.', + tokenRequired: '토큰이 필요합니다.', + }, + fields: { + description: { + label: '설명', + placeholder: '새 사용자 생성 시 gmail 계정을 생성하는 웹훅', + }, + token: { + label: '비밀 토큰', + placeholder: '인증 토큰', + }, + url: { + label: '웹훅 URL', + placeholder: 'https://example.com/webhook', + }, + userCreated: { + label: '새 사용자 생성됨', + }, + userDeleted: { + label: '사용자 삭제됨', + }, + userModified: { + label: '사용자 수정됨', + }, + hwkeyProvision: { + label: '사용자 Yubikey 프로비저닝', + }, + }, + }, + }, + deleteWebhook: { + title: '웹훅 삭제', + message: '{name: string} 웹훅을 삭제하시겠습니까?', + submit: '삭제', + messages: { + success: '웹훅이 삭제되었습니다.', + }, + }, + }, + addDevicePage: { + title: '장치 추가', + helpers: { + setupOpt: `이 마법사를 사용하여 장치를 추가할 수 있습니다. 당사의 기본 애플리케이션인 "defguard" 또는 다른 WireGuard 클라이언트를 선택하세요. 잘 모르시겠다면 간편하게 defguard를 사용하는 것을 권장합니다.`, + client: `defguard 데스크톱 클라이언트는 여기에서 다운로드하고 이 가이드를 따르세요.`, + }, + messages: { + deviceAdded: '장치가 추가되었습니다', + }, + steps: { + setupMethod: { + remote: { + title: '데스크톱 클라이언트 구성', + subTitle: + '단일 토큰으로 간편하게 설정할 수 있습니다. 클라이언트를 다운로드하고 간단한 보안을 즐기세요.', + link: 'defguard 클라이언트 다운로드', + }, + manual: { + title: '수동 WireGuard 클라이언트', + subTitle: + '고급 사용자의 경우 다운로드 또는 QR 코드를 통해 고유한 구성을 얻으세요. 클라이언트를 다운로드하고 VPN 설정을 제어하세요.', + link: 'WireGuard 클라이언트 다운로드', + }, + }, + configDevice: { + title: '장치 구성', + messages: { + copyConfig: '구성이 클립보드에 복사되었습니다', + }, + helpers: { + warningAutoMode: ` +

+ 개인 키를 저장하지 않으므로 + 지금 구성을 다운로드해야 합니다. + 이 페이지가 닫히면 전체 구성 파일(개인 키 포함, 빈 템플릿만)을 + 가져올 수 없습니다. +

+`, + warningManualMode: ` +

+ 여기에 제공된 구성에는 개인 키가 포함되어 있지 않으며 공개 키를 사용하여 채워져 있습니다. 구성이 제대로 작동하려면 직접 교체해야 합니다. +

+`, + warningNoNetworks: '액세스할 수 있는 네트워크가 없습니다.', + qrHelper: ` +

+ 이 QR 코드를 스캔하여 wireguard 애플리케이션으로 장치를 더 빠르게 설정할 수 있습니다. +

`, + }, + qrInfo: + '아래 제공된 구성 파일을 QR 코드를 스캔하거나 장치의 WireGuard 인스턴스에 파일로 가져와서 사용하세요.', + inputNameLabel: '장치 이름', + qrLabel: 'WireGuard 구성 파일', + }, + setupDevice: { + title: 'VPN 장치 생성', + infoMessage: ` +

+ 장치에서 WireGuardVPN을 구성해야 합니다. 방법을 모르는 경우  + 문서를 참조하세요. +

+`, + options: { + auto: '키 쌍 생성', + manual: '내 공개 키 사용', + }, + form: { + fields: { + name: { + label: '장치 이름', + }, + publicKey: { + label: '공개 키 제공', + }, + }, + errors: { + name: { + duplicatedName: '이 이름을 가진 장치가 이미 존재합니다', + }, + }, + }, + }, + copyToken: { + title: '클라이언트 활성화', + tokenCardTitle: '활성화 토큰', + urlCardTitle: 'Defguard 인스턴스 URL', + }, + }, + }, + userPage: { + title: { + view: '사용자 프로필', + edit: '사용자 프로필 편집', + }, + messages: { + editSuccess: '사용자가 업데이트되었습니다.', + failedToFetchUserData: '사용자 정보를 가져올 수 없습니다.', + passwordResetEmailSent: '비밀번호 재설정 이메일이 전송되었습니다.', + }, + userDetails: { + header: '프로필 세부 정보', + messages: { + deleteApp: '앱 및 모든 토큰이 삭제되었습니다.', + }, + warningModals: { + title: '경고', + content: { + usernameChange: `사용자 이름을 변경하면 Defguard를 사용하여 로그인한 서비스에 큰 영향을 미칩니다. 사용자 이름을 변경하면 사용자가 애플리케이션에 대한 액세스 권한을 잃을 수 있습니다(애플리케이션에서 해당 사용자를 인식하지 못하기 때문에). 계속 진행하시겠습니까?`, + emailChange: `외부 OpenID Connect(OIDC) 공급자를 사용하여 사용자를 인증하는 경우 사용자의 이메일 주소를 변경하면 Defguard에 로그인하는 기능에 큰 영향을 미칠 수 있습니다. 계속 진행하시겠습니까?`, + }, + buttons: { + proceed: '진행', + cancel: '취소', + }, + }, + fields: { + username: { + label: '사용자 이름', + }, + firstName: { + label: '이름', + }, + lastName: { + label: '성', + }, + phone: { + label: '전화번호', + }, + email: { + label: '이메일', + }, + status: { + label: '상태', + active: '활성', + disabled: '비활성', + }, + groups: { + label: '사용자 그룹', + noData: '그룹 없음', + }, + apps: { + label: '승인된 앱', + noData: '승인된 앱 없음', + }, + }, + }, + userAuthInfo: { + header: '비밀번호 및 인증', + password: { + header: '비밀번호 설정', + changePassword: '비밀번호 변경', + }, + recovery: { + header: '복구 옵션', + codes: { + label: '복구 코드', + viewed: '조회됨', + }, + }, + mfa: { + header: '이중 인증 방법', + edit: { + disable: 'MFA 비활성화', + }, + messages: { + mfaDisabled: 'MFA가 비활성화되었습니다.', + OTPDisabled: '일회용 비밀번호가 비활성화되었습니다.', + EmailMFADisabled: '이메일 MFA가 비활성화되었습니다.', + changeMFAMethod: 'MFA 방법이 변경되었습니다', + }, + securityKey: { + singular: '보안 키', + plural: '보안 키', + }, + default: '기본값', + enabled: '활성화됨', + disabled: '비활성화됨', + wallet: { + singular: '지갑', + plural: '지갑', + }, + labels: { + totp: '시간 기반 일회용 비밀번호', + email: '이메일', + webauth: '보안 키', + wallets: '지갑', + }, + editMode: { + enable: '활성화', + disable: '비활성화', + makeDefault: '기본값으로 설정', + webauth: { + manage: '보안 키 관리', + }, + }, + }, + }, + controls: { + editButton: '프로필 편집', + deleteAccount: '계정 삭제', + }, + devices: { + header: '사용자 장치', + addDevice: { + web: '새 장치 추가', + desktop: '이 장치 추가', + }, + card: { + labels: { + publicIP: '공개 IP', + connectedThrough: '연결 방식', + connectionDate: '연결 날짜', + lastLocation: '마지막 연결 위치', + lastConnected: '마지막 연결', + assignedIp: '할당된 IP', + active: '활성', + noData: '연결된 적 없음', + }, + edit: { + edit: '장치 편집', + delete: '장치 삭제', + showConfigurations: '구성 보기', + }, + }, + }, + wallets: { + messages: { + addressCopied: '주소가 복사되었습니다.', + duplicate: { + primary: '연결된 지갑이 이미 등록되어 있습니다', + sub: '사용되지 않은 지갑을 연결하세요.', + }, + }, + header: '사용자 지갑', + addWallet: '새 지갑 추가', + card: { + address: '주소', + mfaBadge: 'MFA', + edit: { + enableMFA: 'MFA 활성화', + disableMFA: 'MFA 비활성화', + delete: '삭제', + copyAddress: '주소 복사', + }, + messages: { + deleteSuccess: '지갑이 삭제되었습니다', + enableMFA: '지갑 MFA가 활성화되었습니다', + disableMFA: '지갑 MFA가 비활성화되었습니다', + }, + }, + }, + yubiKey: { + header: '사용자 YubiKey', + provision: 'YubiKey 프로비저닝', + keys: { + pgp: 'PGP 키', + ssh: 'SSH 키', + }, + noLicense: { + moduleName: 'YubiKey 모듈', + line1: 'YubiKey 관리 및 프로비저닝을 위한 엔터프라이즈 모듈입니다.', + line2: '', + }, + }, + authenticationKeys: { + header: '사용자 인증 키', + addKey: '새 키 추가', + keysList: { + common: { + rename: '이름 변경', + key: '키', + download: '다운로드', + copy: '복사', + serialNumber: '시리얼 번호', + delete: '삭제', + }, + }, + deleteModal: { + title: '인증 키 삭제', + confirmMessage: '{name: string} 키가 영구적으로 삭제됩니다.', + }, + addModal: { + header: '새 인증 키 추가', + keyType: '키 유형', + keyForm: { + placeholders: { + title: '키 이름', + key: { + ssh: 'ssh-rsa, ecdsa-sha2-nistp256, ... 로 시작', + gpg: '-----BEGIN PGP PUBLIC KEY BLOCK----- 로 시작', + }, + }, + labels: { + title: '이름', + key: '키', + }, + submit: '{name: string} 키 추가', + }, + yubikeyForm: { + selectWorker: { + info: '이 작업은 YubiKey의 openpgp 애플리케이션을 삭제하고 재구성합니다.', + selectLabel: + '다음 프로비저너 중 하나를 선택하여 YubiKey를 프로비저닝하십시오', + noData: '현재 등록된 작업자가 없습니다.', + available: '사용 가능', + unavailable: '사용 불가', + }, + provisioning: { + inProgress: '프로비저닝 진행 중, 잠시 기다려 주세요.', + error: '프로비저닝 실패!', + success: 'Yubikey가 성공적으로 프로비저닝되었습니다', + }, + submit: 'Yubikey 프로비저닝', + }, + messages: { + keyAdded: '키가 추가되었습니다.', + keyExists: '키가 이미 추가되었습니다.', + unsupportedKeyFormat: '지원되지 않는 키 형식입니다.', + genericError: '키를 추가할 수 없습니다. 나중에 다시 시도하십시오.', + }, + }, + }, + }, + usersOverview: { + pageTitle: '사용자', + search: { + placeholder: '사용자 찾기', + }, + filterLabels: { + all: '모든 사용자', + admin: '관리자만', + users: '사용자만', + }, + usersCount: '모든 사용자', + addNewUser: '새 추가', + list: { + headers: { + name: '사용자 이름', + username: '로그인', + phone: '전화', + actions: '작업', + }, + editButton: { + changePassword: '비밀번호 변경', + edit: '계정 편집', + addYubikey: 'YubiKey 추가', + addSSH: 'SSH 키 추가', + addGPG: 'GPG 키 추가', + delete: '계정 삭제', + startEnrollment: '등록 시작', + activateDesktop: '데스크톱 클라이언트 구성', + resetPassword: '비밀번호 재설정', + }, + }, + }, + navigation: { + bar: { + overview: 'VPN 개요', + users: '사용자', + provisioners: 'YubiKeys', + webhooks: 'Webhooks', + openId: 'OpenID 앱', + myProfile: '내 프로필', + settings: '설정', + logOut: '로그아웃', + enrollment: '등록', + support: '지원', + groups: '그룹', + }, + mobileTitles: { + groups: '그룹', + wizard: 'Location 생성', + users: '사용자', + settings: '설정', + user: '사용자 프로필', + provisioners: 'Yubikey', + webhooks: 'Webhooks', + openId: 'OpenId 앱', + overview: '위치 개요', + networkSettings: '위치 편집', + enrollment: '등록', + support: '지원', + }, + copyright: 'Copyright ©2023-2024', + version: { + open: '애플리케이션 버전: {version: string}', + closed: 'v{version: string}', + }, + }, + form: { + download: '다운로드', + copy: '복사', + saveChanges: '변경 사항 저장', + submit: '제출', + login: '로그인', + cancel: '취소', + close: '닫기', + placeholders: { + password: '비밀번호', + username: '사용자 이름', + }, + error: { + forbiddenCharacter: '필드에 금지된 문자가 포함되어 있습니다.', + usernameTaken: '사용자 이름이 이미 사용 중입니다.', + invalidKey: '키가 유효하지 않습니다.', + invalid: '필드가 유효하지 않습니다.', + required: '필드는 필수입니다.', + invalidCode: '제출된 코드가 유효하지 않습니다.', + maximumLength: '최대 길이를 초과했습니다.', + minimumLength: '최소 길이에 도달하지 않았습니다.', + noSpecialChars: '특수 문자는 허용되지 않습니다.', + oneDigit: '숫자 하나가 필요합니다.', + oneSpecial: '특수 문자가 필요합니다.', + oneUppercase: '대문자 하나가 필요합니다.', + oneLowercase: '소문자 하나가 필요합니다.', + portMax: '최대 포트는 65535입니다.', + endpoint: '유효한 엔드포인트를 입력하세요.', + address: '유효한 주소를 입력하세요.', + validPort: '유효한 포트를 입력하세요.', + validCode: '코드는 6자리여야 합니다.', + allowedIps: '유효한 IP 또는 도메인만 허용됩니다.', + startFromNumber: '숫자로 시작할 수 없습니다.', + repeat: `필드가 일치하지 않습니다.`, + number: '유효한 숫자를 입력해야 합니다.', + minimumValue: `{value: number}의 최솟값에 도달하지 않았습니다.`, + maximumValue: '{value: number}의 최댓값을 초과했습니다.', + tooManyBadLoginAttempts: `잘못된 로그인 시도가 너무 많습니다. 몇 분 후에 다시 시도하십시오.`, + }, + floatingErrors: { + title: '다음을 수정하십시오:', + }, + }, + components: { + deviceConfigsCard: { + cardTitle: '위치에 대한 WireGuard 구성:', + messages: { + copyConfig: '클립보드에 구성이 복사되었습니다.', + }, + }, + gatewaysStatus: { + label: '게이트웨이', + states: { + connected: '모두 연결됨', + partial: '하나 이상 작동하지 않음', + disconnected: '연결 끊김', + error: '연결 정보를 가져오는 데 실패했습니다.', + loading: '연결 정보를 가져오는 중', + }, + messages: { + error: '게이트웨이 상태를 가져오지 못했습니다', + deleteError: '게이트웨이를 삭제하지 못했습니다', + }, + }, + noLicenseBox: { + footer: { + get: '엔터프라이즈 라이선스 받기', + contact: '연락처:', + }, + }, + }, + settingsPage: { + title: '설정', + tabs: { + smtp: 'SMTP', + global: '전역 설정', + ldap: 'LDAP', + openid: 'OpenID', + enterprise: '엔터프라이즈 기능', + }, + messages: { + editSuccess: '설정이 업데이트되었습니다', + challengeSuccess: '챌린지 메시지가 변경되었습니다', + }, + enterpriseOnly: { + title: '이 기능은 Defguard Enterprise에서만 사용할 수 있습니다.', + subtitle: '자세한 내용은 ', + website: '웹사이트', + }, + ldapSettings: { + title: 'LDAP 설정', + form: { + labels: { + ldap_url: 'URL', + ldap_bind_username: '바인드 사용자 이름', + ldap_bind_password: '바인드 비밀번호', + ldap_member_attr: '멤버 속성', + ldap_username_attr: '사용자 이름 속성', + ldap_user_obj_class: '사용자 객체 클래스', + ldap_user_search_base: '사용자 검색 기준', + ldap_groupname_attr: '그룹 이름 속성', + ldap_group_search_base: '그룹 검색 기준', + ldap_group_member_attr: '그룹 멤버 속성', + ldap_group_obj_class: '그룹 객체 클래스', + }, + delete: '구성 삭제', + }, + test: { + title: 'LDAP 연결 테스트', + submit: '테스트', + messages: { + success: 'LDAP 연결 성공', + error: 'LDAP 연결 거부됨', + }, + }, + }, + openIdSettings: { + general: { + title: '외부 OpenID 설정', + helper: '여기에서 Defguard 인스턴스의 일반 OpenID 동작을 변경할 수 있습니다.', + createAccount: { + label: '외부 OpenID를 통해 처음 로그인할 때 사용자 계정을 자동으로 생성합니다.', + helper: + '이 옵션을 활성화하면 Defguard는 외부 OpenID 공급자를 사용하여 처음 로그인하는 사용자에 대한 새 계정을 자동으로 생성합니다. 그렇지 않으면 관리자가 먼저 사용자 계정을 생성해야 합니다.', + }, + }, + form: { + title: '외부 OpenID 클라이언트 설정', + helper: + '여기에서 외부 OpenID 공급자가 제공한 값으로 OpenID 클라이언트 설정을 구성할 수 있습니다.', + custom: '사용자 정의', + documentation: '설명서', + delete: '공급자 삭제', + labels: { + provider: { + label: '공급자', + helper: + 'OpenID 공급자를 선택하세요. 사용자 정의 공급자를 사용하고 직접 기본 URL을 입력할 수 있습니다.', + }, + client_id: { + label: '클라이언트 ID', + helper: 'OpenID 공급자가 제공한 클라이언트 ID입니다.', + }, + client_secret: { + label: '클라이언트 보안 비밀', + helper: 'OpenID 공급자가 제공한 클라이언트 보안 비밀입니다.', + }, + base_url: { + label: '기본 URL', + helper: + 'OpenID 공급자의 기본 URL입니다(예: https://accounts.google.com). 자세한 정보 및 예는 설명서를 확인하십시오.', + }, + }, + }, + }, + modulesVisibility: { + header: '모듈 가시성', + helper: `

+ 사용하지 않는 모듈이 있는 경우 해당 모듈의 가시성을 비활성화할 수 있습니다. +

+ + 자세한 내용은 설명서를 참조하십시오. + `, + fields: { + wireguard_enabled: { + label: 'WireGuard VPN', + }, + webhooks_enabled: { + label: '웹훅', + }, + worker_enabled: { + label: 'Yubikey 프로비저닝', + }, + openid_enabled: { + label: 'OpenID Connect', + }, + }, + }, + defaultNetworkSelect: { + header: '기본 위치 보기', + helper: `

여기에서 기본 위치 보기를 변경할 수 있습니다.

+ + 자세한 내용은 설명서를 참조하십시오. + `, + filterLabels: { + grid: '그리드 보기', + list: '목록 보기', + }, + }, + web3Settings: { + header: 'Web3 / Wallet connect', + fields: { + signMessage: { + label: '기본 서명 메시지 템플릿', + }, + }, + controls: { + save: '변경 사항 저장', + }, + }, + instanceBranding: { + header: '인스턴스 브랜딩', + form: { + title: '이름 및 로고:', + fields: { + instanceName: { + label: '인스턴스 이름', + placeholder: 'Defguard', + }, + mainLogoUrl: { + label: '로그인 로고 url', + helper: '

최대 사진 크기는 250x100 px입니다

', + placeholder: '기본 이미지', + }, + navLogoUrl: { + label: '메뉴 및 탐색 작은 로고', + helper: '

최대 사진 크기는 100x100 px입니다

', + placeholder: '기본 이미지', + }, + }, + controls: { + restoreDefault: '기본값 복원', + submit: '변경 사항 저장', + }, + }, + helper: ` +

+ 여기에서 defguard 인스턴스의 로고 및 이름 url을 + 추가할 수 있습니다. defguard 대신 표시됩니다. +

+ + 자세한 내용은 설명서를 참조하십시오. + + `, + }, + license: { + header: '엔터프라이즈', + helpers: { + enterpriseHeader: { + text: '여기에서 Defguard Enterprise 버전 라이선스를 관리할 수 있습니다.', + link: 'Defguard Enterprise에 대한 자세한 내용은 웹사이트를 방문하십시오.', + }, + licenseKey: { + text: '아래에 Defguard Enterprise 라이선스 키를 입력하세요. 라이선스 구매 후 이메일을 통해 받아야 합니다.', + link: '라이선스는 여기에서 구입할 수 있습니다.', + }, + }, + form: { + title: '라이선스', + fields: { + key: { + label: '라이선스 키', + placeholder: 'Defguard 라이선스 키', + }, + }, + }, + licenseInfo: { + title: '라이선스 정보', + noLicense: '라이선스 없음', + types: { + subscription: { + label: '구독', + helper: '정기적으로 자동 갱신되는 라이선스', + }, + offline: { + label: '오프라인', + helper: '라이선스는 만료 날짜까지 유효하며 자동으로 갱신되지 않습니다', + }, + }, + fields: { + type: { + label: '유형', + }, + validUntil: { + label: '유효 기간', + }, + }, + }, + }, + smtp: { + form: { + title: 'SMTP 구성', + fields: { + encryption: { + label: '암호화', + }, + server: { + label: '서버 주소', + placeholder: '주소', + }, + port: { + label: '서버 포트', + placeholder: '포트', + }, + user: { + label: '서버 사용자 이름', + placeholder: '사용자 이름', + }, + password: { + label: '서버 비밀번호', + placeholder: '비밀번호', + }, + sender: { + label: '보내는 사람 이메일 주소', + placeholder: '주소', + helper: ` +

+ 시스템 메시지는 이 주소에서 발송됩니다. + 예: no-reply@my-company.com. +

+ `, + }, + }, + controls: { + submit: '변경 사항 저장', + }, + }, + delete: '구성 삭제', + testForm: { + title: '테스트 이메일 보내기', + fields: { + to: { + label: '주소', + placeholder: '주소', + }, + }, + controls: { + submit: '보내기', + success: '테스트 이메일 전송됨', + error: '이메일 전송 오류', + }, + }, + helper: ` +

+ 여기에서 사용자에게 시스템 메시지를 보내는 데 사용되는 SMTP 서버를 구성할 수 있습니다. +

+ `, + }, + enrollment: { + helper: + '등록은 신규 직원이 새 계정을 활성화 및 비밀번호를 생성하고, VPN 장치를 구성할 수 있도록 하는 프로세스입니다.', + vpnOptionality: { + header: 'VPN 단계 선택 사항', + helper: + '등록 중 VPN 장치 생성을 선택 사항 또는 필수 사항으로 선택할 수 있습니다.', + }, + welcomeMessage: { + header: '환영 메시지', + helper: ` +

이 텍스트 입력란에서는 Markdown을 사용할 수 있습니다:

+
    +
  • 제목은 해시 #로 시작합니다
  • +
  • 별표를 사용하여 *이탤릭체*를 만듭니다
  • +
  • 별표 두 개를 사용하여 **굵게** 만듭니다
  • +
+ `, + }, + welcomeEmail: { + header: '환영 이메일', + helper: ` +

이 텍스트 입력란에서는 Markdown을 사용할 수 있습니다:

+
    +
  • 제목은 해시 #로 시작합니다
  • +
  • 별표를 사용하여 *이탤릭체*를 만듭니다
  • +
  • 별표 두 개를 사용하여 **굵게** 만듭니다
  • +
+ `, + }, + form: { + controls: { + submit: '변경 사항 저장', + }, + welcomeMessage: { + helper: + '등록이 완료되면 사용자에게 이 정보가 표시됩니다. 관련 링크를 삽입하고 다음 단계를 간략하게 설명하는 것이 좋습니다.', + placeholder: '환영 메시지를 입력하세요', + }, + welcomeEmail: { + helper: + '등록이 완료되면 사용자에게 이 정보가 전송됩니다. 관련 링크를 삽입하고 다음 단계를 간략하게 설명하는 것이 좋습니다. 환영 메시지를 여기에서 다시 사용할 수 있습니다.', + placeholder: '환영 이메일을 입력하세요', + }, + welcomeEmailSubject: { + label: '제목', + }, + useMessageAsEmail: { + label: '환영 메시지와 동일하게', + }, + }, + }, + enterprise: { + header: '엔터프라이즈 기능', + helper: '

여기에서 엔터프라이즈 설정을 변경할 수 있습니다.

', + fields: { + deviceManagement: { + label: '사용자가 자신의 장치를 관리하는 기능 비활성화', + helper: + '이 옵션을 활성화하면 관리자 그룹의 사용자만 사용자 프로필에서 장치를 관리할 수 있습니다(다른 모든 사용자는 비활성화됨)', + }, + manualConfig: { + label: '사용자가 수동 WireGuard 구성을 다운로드하는 기능 비활성화', + helper: + '이 옵션을 활성화하면 사용자에게 수동 클라이언트 설정을 위한 WireGuard 구성이 표시되지 않습니다.', + }, + }, + }, + }, + openidOverview: { + pageTitle: 'OpenID 앱', + search: { + placeholder: '앱 찾기', + }, + filterLabels: { + all: '모든 앱', + enabled: '활성화됨', + disabled: '비활성화됨', + }, + clientCount: '모든 앱', + addNewApp: '새 추가', + list: { + headers: { + name: '이름', + status: '상태', + actions: '작업', + }, + editButton: { + edit: '앱 편집', + delete: '앱 삭제', + disable: '비활성화', + enable: '활성화', + copy: '클라이언트 ID 복사', + }, + status: { + enabled: '활성화됨', + disabled: '비활성화됨', + }, + }, + messages: { + copySuccess: '클라이언트 ID가 복사되었습니다.', + noLicenseMessage: '이 기능에 대한 라이선스가 없습니다.', + noClientsFound: '결과를 찾을 수 없습니다.', + }, + deleteApp: { + title: '앱 삭제', + message: '{appName: string} 앱을 삭제하시겠습니까?', + submit: '앱 삭제', + messages: { + success: '앱이 삭제되었습니다.', + }, + }, + enableApp: { + messages: { + success: '앱이 활성화되었습니다.', + }, + }, + disableApp: { + messages: { + success: '앱이 비활성화되었습니다.', + }, + }, + modals: { + openidClientModal: { + title: { + addApp: '애플리케이션 추가', + editApp: '{appName: string} 앱 편집', + }, + scopes: '범위:', + messages: { + clientIdCopy: '클라이언트 ID 복사됨.', + clientSecretCopy: '클라이언트 암호 복사됨.', + }, + form: { + messages: { + successAdd: '앱 생성됨.', + successModify: '앱 수정됨.', + }, + error: { + urlRequired: 'URL이 필요합니다.', + validUrl: '유효한 URL이어야 합니다.', + scopeValidation: '최소 하나의 범위가 있어야 합니다.', + }, + fields: { + name: { + label: '앱 이름', + }, + redirectUri: { + label: '리디렉션 URL {count: number}', + placeholder: 'https://example.com/redirect', + }, + openid: { + label: 'OpenID', + }, + profile: { + label: '프로필', + }, + email: { + label: '이메일', + }, + phone: { + label: '전화', + }, + groups: { + label: '그룹', + }, + }, + controls: { + addUrl: 'URL 추가', + }, + }, + clientId: '클라이언트 ID', + clientSecret: '클라이언트 암호', + }, + }, + }, + webhooksOverview: { + pageTitle: 'Webhooks', + search: { + placeholder: 'URL로 웹훅 찾기', + }, + filterLabels: { + all: '모든 웹훅', + enabled: '활성화됨', + disabled: '비활성화됨', + }, + webhooksCount: '모든 웹훅', + addNewWebhook: '새 추가', + noWebhooksFound: '웹훅을 찾을 수 없습니다.', + list: { + headers: { + name: '이름', + description: '설명', + status: '상태', + actions: '작업', + }, + editButton: { + edit: '편집', + delete: '웹훅 삭제', + disable: '비활성화', + enable: '활성화', + }, + status: { + enabled: '활성화됨', + disabled: '비활성화됨', + }, + }, + }, + provisionersOverview: { + pageTitle: '프로비저너', + search: { + placeholder: '프로비저너 찾기', + }, + filterLabels: { + all: '전체', + available: '사용 가능', + unavailable: '사용 불가', + }, + provisionersCount: '모든 프로비저너', + noProvisionersFound: '프로비저너를 찾을 수 없습니다.', + noLicenseMessage: '이 기능에 대한 라이선스가 없습니다.', + provisioningStation: { + header: 'YubiKey 프로비저닝 스테이션', + content: `YubiKeys를 프로비저닝하려면 먼저 USB 슬롯이 있는 물리적 시스템을 + 설정해야 합니다. 선택한 시스템에서 제공된 명령을 실행하여 등록하고 + 키 프로비저닝을 시작하세요.`, + dockerCard: { + title: '프로비저닝 스테이션 도커 설정 명령', + }, + tokenCard: { + title: '액세스 토큰', + }, + }, + list: { + headers: { + name: '이름', + ip: 'IP 주소', + status: '상태', + actions: '작업', + }, + editButton: { + delete: '프로비저너 삭제', + }, + status: { + available: '사용 가능', + unavailable: '사용 불가', + }, + }, + messages: { + copy: { + token: '토큰 복사됨', + command: '명령 복사됨', + }, + }, + }, + openidAllow: { + header: '{name: string}이(가) 다음을 원합니다:', + scopes: { + openid: '향후 로그인을 위해 프로필 데이터를 사용합니다.', + profile: '이름, 프로필 사진 등 프로필의 기본 정보를 알고 있습니다.', + email: '이메일 주소를 알고 있습니다.', + phone: '전화번호를 알고 있습니다.', + groups: '그룹 멤버십을 알고 있습니다.', + }, + controls: { + accept: '수락', + cancel: '취소', + }, + }, + networkOverview: { + pageTitle: '위치 개요', + controls: { + editNetworks: '위치 설정 편집', + selectNetwork: { + placeholder: '위치 로드 중', + }, + }, + filterLabels: { + grid: '그리드 보기', + list: '목록 보기', + }, + stats: { + currentlyActiveUsers: '현재 활성 사용자', + currentlyActiveDevices: '현재 활성 장치', + activeUsersFilter: '{hour: number}시간 내 활성 사용자', + activeDevicesFilter: '{hour: number}시간 내 활성 장치', + totalTransfer: '총 전송량:', + activityIn: '{hour: number}시간 내 활동', + in: '들어오는 트래픽:', + out: '나가는 트래픽:', + gatewayDisconnected: '게이트웨이 연결 끊김', + }, + }, + connectedUsersOverview: { + pageTitle: '연결된 사용자', + noUsersMessage: '현재 연결된 사용자가 없습니다', + userList: { + username: '사용자 이름', + device: '장치', + connected: '연결됨', + deviceLocation: '장치 위치', + networkUsage: '네트워크 사용량', + }, + }, + networkPage: { + pageTitle: '위치 편집', + addNetwork: '+ 새 위치 추가', + controls: { + networkSelect: { + label: '위치 선택', + }, + }, + }, + activityOverview: { + header: '활동 스트림', + noData: '현재 감지된 활동이 없습니다', + }, + networkConfiguration: { + messages: { + delete: { + success: '네트워크 삭제됨', + error: '네트워크 삭제 실패', + }, + }, + header: '위치 구성', + importHeader: '위치 가져오기', + form: { + helpers: { + address: + '이 주소를 기반으로 VPN 네트워크 주소가 정의됩니다. 예: 10.10.10.1/24 (VPN 네트워크는 10.10.10.0/24가 됩니다)', + gateway: 'VPN 사용자가 연결하는 데 사용되는 게이트웨이 공개 주소', + dns: 'wireguard 인터페이스가 활성화될 때 쿼리할 DNS 확인자를 지정합니다.', + allowedIps: 'VPN 네트워크를 통해 라우팅되어야 하는 주소/마스크 목록입니다.', + allowedGroups: + '기본적으로 모든 사용자가 이 위치에 연결할 수 있습니다. 특정 그룹으로 이 위치에 대한 액세스를 제한하려면 아래에서 선택하십시오.', + }, + messages: { + networkModified: '위치가 수정되었습니다.', + networkCreated: '위치가 생성되었습니다', + }, + fields: { + name: { + label: '위치 이름', + }, + address: { + label: '게이트웨이 VPN IP 주소 및 넷마스크', + }, + endpoint: { + label: '게이트웨이 주소', + }, + allowedIps: { + label: '허용된 IP', + }, + port: { + label: '게이트웨이 포트', + }, + dns: { + label: 'DNS', + }, + allowedGroups: { + label: '허용된 그룹', + placeholder: '모든 그룹', + }, + mfa_enabled: { + label: '이 위치에 MFA 필요', + }, + keepalive_interval: { + label: 'Keepalive 간격 [초]', + }, + peer_disconnect_threshold: { + label: '피어 연결 끊김 임계값 [초]', + }, + }, + controls: { + submit: '변경 사항 저장', + cancel: '개요로 돌아가기', + delete: '위치 제거', + }, + }, + }, + gatewaySetup: { + header: { + main: '게이트웨이 서버 설정', + dockerBasedGatewaySetup: `Docker 기반 게이트웨이 설정`, + fromPackage: `패키지로부터`, + oneLineInstall: `한 줄 설치`, + }, + card: { + title: 'Docker 기반 게이트웨이 설정', + authToken: `인증 토큰`, + }, + button: { + availablePackages: `사용 가능한 패키지`, + }, + controls: { + status: '연결 상태 확인', + }, + messages: { + runCommand: `Defguard는 vpn 서버에서 wireguard VPN을 제어하기 위해 게이트웨이 노드를 배포해야 합니다. + 자세한 내용은 [문서]({setupGatewayDocs:string})를 참조하십시오. + 게이트웨이 서버를 배포하는 방법에는 여러 가지가 있으며, + 아래는 Docker 기반 예시입니다. 다른 예시는 [문서]({setupGatewayDocs:string})를 참조하십시오.`, + createNetwork: `게이트웨이 프로세스를 실행하기 전에 네트워크를 생성하십시오.`, + noConnection: `연결이 설정되지 않았습니다. 제공된 명령을 실행하십시오.`, + connected: `게이트웨이가 연결되었습니다.`, + statusError: '게이트웨이 상태를 가져오지 못했습니다', + oneLineInstall: `한 줄 설치를 수행하는 경우: https://defguard.gitbook.io/defguard/admin-and-features/setting-up-your-instance/one-line-install + 아무 것도 할 필요가 없습니다.`, + fromPackage: `https://github.com/DefGuard/gateway/releases/latest에서 사용 가능한 패키지를 설치하고 [문서]({setupGatewayDocs:string})에 따라 \`/etc/defguard/gateway.toml\`을 구성하십시오. + `, + authToken: `아래 토큰은 게이트웨이 노드를 인증하고 구성하는 데 필요합니다. 이 토큰을 안전하게 보관하고 + [문서]({setupGatewayDocs:string})에 제공된 배포 지침에 따라 게이트웨이 서버를 성공적으로 설정하십시오. + 자세한 내용 및 정확한 단계는 [문서]({setupGatewayDocs:string})를 참조하십시오.`, + dockerBasedGatewaySetup: `아래는 Docker 기반 예시입니다. 자세한 내용 및 정확한 단계는 [문서]({setupGatewayDocs:string})를 참조하십시오.`, + }, + }, + loginPage: { + pageTitle: '자격 증명을 입력하세요', + callback: { + return: '로그인으로 돌아가기', + error: '외부 OpenID 로그인 중 오류가 발생했습니다', + }, + mfa: { + title: '이중 인증', + controls: { + useAuthenticator: '대신 인증 앱 사용', + useWallet: '대신 지갑 사용', + useWebauthn: '대신 보안 키 사용', + useRecoveryCode: '대신 복구 코드 사용', + useEmail: '대신 이메일 사용', + }, + email: { + header: '이메일로 전송된 코드를 사용하여 진행하십시오.', + form: { + labels: { + code: '코드', + }, + controls: { + resendCode: '코드 재전송', + }, + }, + }, + totp: { + header: '인증 앱의 코드를 사용하고 버튼을 클릭하여 진행하십시오.', + form: { + fields: { + code: { + placeholder: '인증 코드 입력', + }, + }, + controls: { + submit: '인증 코드 사용', + }, + }, + }, + recoveryCode: { + header: '활성 복구 코드 중 하나를 입력하고 버튼을 클릭하여 로그인하십시오.', + form: { + fields: { + code: { + placeholder: '복구 코드', + }, + }, + controls: { + submit: '복구 코드 사용', + }, + }, + }, + wallet: { + header: + '암호화폐 지갑을 사용하여 로그인하려면 지갑 앱 또는 확장 프로그램에서 메시지에 서명하십시오.', + controls: { + submit: '지갑 사용', + }, + messages: { + walletError: '서명 프로세스 중 지갑 연결이 끊어졌습니다.', + walletErrorMfa: + '지갑이 MFA 로그인에 대해 승인되지 않았습니다. 승인된 지갑을 사용하십시오.', + }, + }, + webauthn: { + header: '인증할 준비가 되면 아래 버튼을 누르십시오.', + controls: { + submit: '보안 키 사용', + }, + messages: { + error: '키를 읽지 못했습니다. 다시 시도하십시오.', + }, + }, + }, + }, + wizard: { + completed: '위치 설정 완료', + configuration: { + successMessage: '위치 생성됨', + }, + welcome: { + header: '위치 마법사에 오신 것을 환영합니다!', + sub: 'VPN을 사용하기 전에 먼저 위치를 설정해야 합니다. 확실하지 않은 경우 아이콘을 클릭하십시오.', + button: '위치 설정', + }, + navigation: { + top: '위치 설정', + titles: { + welcome: '위치 설정', + choseNetworkSetup: '위치 설정 선택', + importConfig: '기존 위치 가져오기', + manualConfig: '위치 구성', + mapDevices: '가져온 장치 매핑', + }, + buttons: { + next: '다음', + back: '뒤로', + }, + }, + deviceMap: { + messages: { + crateSuccess: '장치 추가됨', + errorsInForm: '표시된 필드를 채워주세요.', + }, + list: { + headers: { + deviceName: '장치 이름', + deviceIP: 'IP', + user: '사용자', + }, + }, + }, + wizardType: { + manual: { + title: '수동 구성', + description: '수동 위치 구성', + }, + import: { + title: '파일에서 가져오기', + description: 'WireGuard 구성 파일에서 가져오기', + }, + createNetwork: '위치 생성', + }, + common: { + select: '선택', + }, + locations: { + form: { + name: '이름', + ip: 'IP 주소', + user: '사용자', + fileName: '파일', + selectFile: '파일 선택', + messages: { devicesCreated: '장치 생성됨' }, + validation: { invalidAddress: '잘못된 주소' }, + }, + }, + }, + layout: { + select: { + addNewOptionDefault: '새 추가 +', + }, + }, + redirectPage: { + title: '로그인되었습니다', + subtitle: '잠시 후 리디렉션됩니다...', + }, + enrollmentPage: { + title: '등록', + controls: { + default: '기본값 복원', + save: '변경 사항 저장', + }, + messages: { + edit: { + success: '설정이 변경되었습니다', + error: '저장 실패', + }, + }, + messageBox: + '등록은 신입 직원이 새 계정을 확인하고, 비밀번호를 생성하고, VPN 장치를 구성할 수 있도록 하는 프로세스입니다. 이 패널에서 관련 메시지를 사용자 지정할 수 있습니다.', + settings: { + welcomeMessage: { + title: '환영 메시지', + messageBox: + '이 정보는 등록이 완료되면 서비스 내 사용자에게 표시됩니다. 링크를 삽입하고 다음 단계를 간략하게 설명하는 것이 좋습니다. 이메일에 있는 것과 동일한 메시지를 사용할 수 있습니다.', + }, + vpnOptionality: { + title: 'VPN 설정 선택 사항', + select: { + options: { + optional: '선택 사항', + mandatory: '필수', + }, + }, + }, + welcomeEmail: { + title: '환영 이메일', + subject: { + label: '이메일 제목', + }, + messageBox: + '등록이 완료되면 사용자에게 이 정보가 전송됩니다. 관련 링크를 삽입하고 다음 단계를 간략하게 설명하는 것이 좋습니다.', + controls: { + duplicateWelcome: '환영 메시지와 동일', + }, + }, + }, + }, + supportPage: { + title: '지원', + modals: { + confirmDataSend: { + title: '지원 데이터 보내기', + subTitle: + '실제로 지원 디버그 정보를 보내려는 것인지 확인하십시오. 개인 정보는 전송되지 않습니다(wireguard 키, 이메일 주소 등은 전송되지 않음).', + submit: '지원 데이터 보내기', + }, + }, + debugDataCard: { + title: '지원 데이터', + body: ` +지원이 필요하거나 저희 팀(예: Matrix 지원 채널: **#defguard-support:teonite.com**)에서 지원 데이터 생성을 요청받은 경우 다음 두 가지 옵션이 있습니다. +* SMTP 설정을 구성하고 "지원 데이터 보내기"를 클릭합니다. +* 또는 "지원 데이터 다운로드"를 클릭하고 이 파일을 첨부하여 GitHub에 버그 보고서를 생성합니다. +`, + downloadSupportData: '지원 데이터 다운로드', + downloadLogs: '로그 다운로드', + sendMail: '지원 데이터 보내기', + mailSent: '이메일 전송됨', + mailError: '이메일 전송 오류', + }, + supportCard: { + title: '지원', + body: ` +GitHub에 문의하거나 문제를 제출하기 전에 [defguard.gitbook.io/defguard](https://defguard.gitbook.io/defguard/)에서 제공되는 Defguard 문서를 숙지하십시오. + +제출하려면: +* 버그 - [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=bug&template=bug_report.md&title=)로 이동하십시오. +* 기능 요청 - [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=feature&template=feature_request.md&title=)로 이동하십시오. + +기타 요청은 support@defguard.net으로 문의하십시오. +`, + }, + }, +}; + +export default ko; diff --git a/web/src/i18n/pl/index.ts b/web/src/i18n/pl/index.ts index 8006c3719c..e9a8c72c3b 100644 --- a/web/src/i18n/pl/index.ts +++ b/web/src/i18n/pl/index.ts @@ -32,6 +32,7 @@ const pl: Translation = { error: 'Wystąpił błąd.', success: 'Operacja zakończyła się sukcesem', errorVersion: 'Nie udało się uzyskać wersji aplikacji.', + details: 'Szczegóły:', clipboard: { success: 'Skopiowano do schowka', error: 'Schowek nie jest dostępny', @@ -202,7 +203,7 @@ const pl: Translation = { totpCopied: 'Ścieżka TOTP skopiowana.', success: 'TOTP Enabled', }, - copyPath: 'Skopiowana ścieżka TOTP', + copyPath: 'Kopiuj ścieżkę TOTP', form: { fields: { code: { @@ -369,7 +370,7 @@ const pl: Translation = { }, lastName: { placeholder: 'Nazwisko', - label: 'Ostatnie imię', + label: 'Nazwisko', }, phone: { placeholder: 'Telefon', @@ -377,6 +378,7 @@ const pl: Translation = { }, enableEnrollment: { label: 'Użyj zdalnej rejestracji', + link: 'więcej informacji tutaj', }, }, }, @@ -446,7 +448,9 @@ const pl: Translation = { }, helpers: { setupOpt: `Możesz dodać urządzenie używając naszego klienta lub samemu skonfigurwać urządzenie.`, + client: `Pobierz klienta defguard tutaj, a następnie postępuj zgodnie z instrukcją w celu jego konfiguracji.`, }, + steps: { setupDevice: { title: 'Dodaj urządzenie', @@ -526,6 +530,17 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe messages: { deleteApp: 'Aplikacja i wszystkie tokeny usunięte.', }, + warningModals: { + title: 'Ostrzeżenie', + content: { + usernameChange: `Zmiana nazwy użytkownika ma znaczący wpływ na usługi, do których użytkownik zalogował się za pomocą Defguard. Po zmianie nazwy użytkownika użytkownik może stracić do nich dostęp (ponieważ nie będą go rozpoznawać). Czy na pewno chcesz kontynuować?`, + emailChange: `Jeśli korzystasz z zewnętrznych dostawców OpenID Connect (OIDC) do uwierzytelniania użytkowników, zmiana adresu e-mail użytkownika może mieć wpływ na jego możliwość zalogowania się do Defguarda. Czy na pewno chcesz kontynuować?`, + }, + buttons: { + proceed: 'Proceed', + cancel: 'Cancel', + }, + }, fields: { username: { label: 'Nazwa użytkownika', @@ -793,10 +808,10 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe support: 'Wsparcie', groups: 'Grupy', }, - copyright: 'Copyright \u00A9 2023-2024', + copyright: 'Copyright ©2023-2024', version: { open: 'Wersja aplikacji: {version}', - closed: 'v {version}', + closed: 'v{version}', }, }, form: { @@ -804,7 +819,7 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe copy: 'Kopiuj', saveChanges: 'Zapisz zmiany', submit: 'Zapisz', - login: 'Zaloguj sie', + login: 'Zaloguj się', cancel: 'Anuluj', close: 'Zamknij', placeholders: { @@ -835,6 +850,8 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe repeat: 'Wartości się nie pokrywają.', maximumValue: 'Maksymalna wartość {value} przekroczona.', minimumValue: 'Minimalna wartość {value} nie osiągnięta.', + tooManyBadLoginAttempts: + 'Zbyt duża ilość nieprawidłowego logowania. Spróbuj ponownie za kilka minut.', number: 'Wartość musi być liczbą.', }, floatingErrors: { @@ -875,11 +892,19 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe smtp: 'SMTP', global: 'Globalne', ldap: 'LDAP', + openid: 'OpenID', + enterprise: 'Funkcjonalności enterprise', }, messages: { editSuccess: 'Ustawienia zaktualizowane.', challengeSuccess: 'Zmieniono wiadomość do podpisu.', }, + enterpriseOnly: { + title: 'Ta funkcja jest dostępna tylko w wersji Defguard Enterprise', + currentExpired: 'Twoja obecna licencja wygasła.', + subtitle: 'Aby uzyskać więcej informacji, odwiedź naszą ', + website: 'stronę internetową', + }, ldapSettings: { title: 'Ustawienia LDAP', form: { @@ -896,6 +921,7 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe ldap_group_member_attr: 'Group Member Attribute', ldap_group_obj_class: 'Group Object Class', }, + delete: 'Usuń konfigurację', }, test: { title: 'Test połączenia LDAP', @@ -906,10 +932,51 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe submit: 'Test', }, }, + openIdSettings: { + general: { + title: 'Ustawienia zewnętrznego OpenID', + helper: + 'Możesz tu zmienić ogólną mechanikę działania zewnętrznego OpenID w twojej instancji Defguarda.', + createAccount: { + label: + 'Automatycznie twórz konta w momencie logowania przez zewnętrznego dostawcę OpenID', + helper: + 'Jeśli ta opcja jest włączona, Defguard automatycznie tworzy nowe konta dla użytkowników, którzy logują się po raz pierwszy za pomocą zewnętrznego dostawcy OpenID. W innym przypadku konto użytkownika musi zostać najpierw utworzone przez administratora.', + }, + }, + form: { + title: 'Ustawienia klienta zewnętrznego OpenID', + helper: + 'Tutaj możesz skonfigurować ustawienia klienta OpenID z wartościami dostarczonymi przez zewnętrznego dostawcę OpenID.', + custom: 'Niestandardowy', + documentation: 'Dokumentacja', + delete: 'Usuń dostawcę', + labels: { + provider: { + label: 'Dostawca', + helper: + 'Wybierz swojego dostawcę OpenID. Możesz użyć dostawcy niestandardowego i samodzielnie wypełnić pole URL bazowego.', + }, + client_id: { + label: 'ID klienta', + helper: 'ID klienta dostarczone przez dostawcę OpenID.', + }, + client_secret: { + label: 'Sekret klienta', + helper: 'Sekret klienta dostarczony przez dostawcę OpenID.', + }, + base_url: { + label: 'URL bazowy', + helper: + 'Podstawowy adres URL twojego dostawcy OpenID, np. https://accounts.google.com. Sprawdź naszą dokumentację, aby uzyskać więcej informacji i zobaczyć przykłady.', + }, + }, + }, + }, modulesVisibility: { header: 'Widoczność modułów', helper: `

- Jeśli nie używasz niektórych modułów możesz zmienić ich widoczność + Jeśli nie używasz niektórych modułów, możesz zmienić ich widoczność

Przeczytaj więcej w dokumentacji. @@ -986,6 +1053,57 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe `, }, + license: { + header: 'Funkcje enterprise', + helpers: { + enterpriseHeader: { + text: 'Tutaj możesz zarządzać swoją licencją Defguard Enterprise.', + link: 'By dowiedzieć się więcej, odwiedź naszą stronę.', + }, + licenseKey: { + text: 'Wprowadź poniżej klucz licencyjny Defguard Enterprise. Powinieneś otrzymać go na swoją skrzynkę e-mailową po zakupie licencji.', + link: 'Licencję możesz zakupić tutaj.', + }, + }, + form: { + title: 'Licencja', + fields: { + key: { + label: 'Klucz licencji', + placeholder: 'Klucz licencji dla twojej instancji Defguard', + }, + }, + }, + licenseInfo: { + title: 'Informacje o licencji', + noLicense: 'Brak licencji', + types: { + subscription: { + label: 'Subskrypcja', + helper: 'Subskrypcja automatycznie odnawiana cyklicznie', + }, + offline: { + label: 'Offline', + helper: 'Licencja ważna do daty wygaśnięcia, odnawiana ręcznie', + }, + }, + fields: { + status: { + label: 'Status', + active: 'Aktywna', + expired: 'Wygasła', + subscriptionHelper: + 'Licencja w formie subskrypcji jest ważna przez pewien czas po dacie wygaśnięcia, by uwzględnić możliwe opóźnienia w automatycznej płatności.', + }, + type: { + label: 'Typ', + }, + validUntil: { + label: 'Ważna do', + }, + }, + }, + }, smtp: { form: { title: 'Ustawienia', @@ -1020,9 +1138,10 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe }, }, controls: { - submit: 'Save changes', + submit: 'Zapisz zmiany', }, }, + delete: 'Usuń konfigurację', testForm: { title: 'Wyślij testowy e-mail', fields: { @@ -1095,6 +1214,27 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe }, }, }, + enterprise: { + header: 'Funkcjonalności Enterprise', + helper: '

Tutaj możesz zmienić ustawienia enterprise.

', + fields: { + deviceManagement: { + label: 'Zablokuj możliwość zarządzania urządzeniami przez użytkowników', + helper: + 'Kiedy ta opcja jest włączona, tylko użytkownicy w grupie "Admin" mogą zarządzać urządzeniami w profilu użytkownika', + }, + disableAllTraffic: { + label: 'Zablokuj możliwość przekierowania całego ruchu przez VPN', + helper: + 'Kiedy ta opcja jest włączona, użytkownicy nie będą mogli przekierować całego ruchu przez VPN za pomocą klienta Defguard.', + }, + manualConfig: { + label: 'Wyłącz manualną konfigurację WireGuard', + helper: + 'Kiedy ta opcja jest włączona, użytkownicy nie będą mogli pobrać ani wyświetlić danych do manualnej konfiguracji WireGuard. Możliwe będzie wyłącznie skonfigurowanie klienta Defguard.', + }, + }, + }, }, openidOverview: { pageTitle: 'Aplikacje OpenID', @@ -1410,32 +1550,48 @@ Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupe }, }, gatewaySetup: { - header: 'Uruchomienie serwera gateway', + header: { + main: 'Uruchomienie serwera gateway', + dockerBasedGatewaySetup: `Konfiguracja gateway za pomocą narzędzia docker`, + fromPackage: `Z pakietu`, + oneLineInstall: `Instalacja za pomocą jednej linii`, + }, card: { title: 'Komenda Dockera uruchamiająca serwer gateway', + authToken: 'Token Autoryzacyjny', + }, + button: { + availablePackages: `Dostępne pakiety`, }, controls: { status: 'Sprawdź status połączenia', }, messages: { - runCommand: ` -

- Defguard wymaga uruchomienia serwera gateway w celu kontrolowania VPN. - Szczegóły znajdziesz w dokumentacji. + runCommand: `Defguard wymaga uruchomienia serwera gateway w celu kontrolowania VPN. + Szczegóły znajdziesz w [dokumentacji]({setupGatewayDocs}). Istnieje wiele sposobów na uruchomienie serwera gateway, poniższy przykład używa technologii Docker, - więcej przykładów znajdziesz w dokumentacji. -

`, - createNetwork: ` -

- Utwórz sieć przed uruchomieniem procesu gateway. -

`, - noConnection: `

Brak połączenia proszę uruchom poniższą komendę.

`, - connected: `

Gateway połączony.

`, + więcej przykładów znajdziesz w [dokumentacji]({setupGatewayDocs}).`, + createNetwork: `Utwórz sieć przed uruchomieniem procesu gateway.`, + noConnection: `Brak połączenia proszę uruchom poniższą komendę.`, + connected: `Gateway połączony.`, statusError: 'Nie udało się uzyskać statusu', + oneLineInstall: `Jeśli wykonujesz instalację w jednej linii: https://defguard.gitbook.io/defguard/admin-and-features/setting-up-your-instance/one-line-install + nie ma potrzeby wykonywania dalszych kroków.`, + fromPackage: `Zainstaluj pakiet dostępny na https://github.com/DefGuard/gateway/releases/latest i skonfiguruj \`/etc/defguard/gateway.toml\` + na podstawie [dokumentacji]({setupGatewayDocs}).`, + authToken: `Poniższy token jest wymwagany do autoryzacji i konfiguracji węzła gateway. Upewnij się, że zachowasz ten token w bezpiecznym miejscu, + a następnie podążaj za instrukcją wdrażania usługi znajdującej się w [dokumentacji]({setupGatewayDocs}), aby pomyślnie skonfigurwoać serwer gateway. + Po więcej szczegółów i dokładnych kroków, proszę zapoznaj się z [dokumentacją](setupGatewayDocs).`, + dockerBasedGatewaySetup: `Poniżej znajduje się przykład oparty na Dockerze. + Więcej szczegółów i dokładnych kroków można znaleźć w [dokumentacji]({setupGatewayDocs}).`, }, }, loginPage: { pageTitle: 'Wprowadź swoje dane logowania', + callback: { + return: 'Powrót do logowania', + error: 'Wystąpił błąd podczas logowania przez zewnętrznego dostawcę OpenID', + }, mfa: { title: 'Autoryzacja dwuetapowa.', controls: { diff --git a/web/src/pages/addDevice/AddDevicePage.tsx b/web/src/pages/addDevice/AddDevicePage.tsx index b21b828ab0..1e2a70e1b8 100644 --- a/web/src/pages/addDevice/AddDevicePage.tsx +++ b/web/src/pages/addDevice/AddDevicePage.tsx @@ -16,6 +16,7 @@ import { ButtonSize, ButtonStyleVariant, } from '../../shared/defguard-ui/components/Layout/Button/types'; +import { useAppStore } from '../../shared/hooks/store/useAppStore'; import { useAddDevicePageStore } from './hooks/useAddDevicePageStore'; import { AddDeviceConfigStep } from './steps/AddDeviceConfigStep/AddDeviceConfigStep'; import { AddDeviceSetupMethodStep } from './steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep'; @@ -29,6 +30,7 @@ export const AddDevicePage = () => { const navigate = useNavigate(); const userData = useAddDevicePageStore((state) => state.userData); + const enterpriseSettings = useAppStore((state) => state.enterprise_settings); const [currentStep, setupMethod] = useAddDevicePageStore( (state) => [state.currentStep, state.method], @@ -68,6 +70,7 @@ export const AddDevicePage = () => { onClick={() => { navigate('/', { replace: true }); }} + disabled={currentStep === 0 && enterpriseSettings?.only_client_activation} />
diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep.tsx b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep.tsx index 3bb8d8bbfa..7d3aaa985e 100644 --- a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep.tsx +++ b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep.tsx @@ -8,8 +8,11 @@ import { useI18nContext } from '../../../../i18n/i18n-react'; import SvgDefguardNavLogo from '../../../../shared/components/svg/DefguardNavLogo'; import SvgWireguardLogo from '../../../../shared/components/svg/WireguardLogo'; import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; +import { LoaderSpinner } from '../../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; import { MessageBoxType } from '../../../../shared/defguard-ui/components/Layout/MessageBox/types'; +import useEffectOnce from '../../../../shared/helpers/useEffectOnce'; +import { useAppStore } from '../../../../shared/hooks/store/useAppStore'; import useApi from '../../../../shared/hooks/useApi'; import { externalLink } from '../../../../shared/links'; import { useAddDevicePageStore } from '../../hooks/useAddDevicePageStore'; @@ -26,6 +29,7 @@ export const AddDeviceSetupMethodStep = () => { const methodRef = useRef(setupMethod); const userData = useAddDevicePageStore((state) => state.userData); + const enterpriseSettings = useAppStore((state) => state.enterprise_settings); const [setPageState, next, nextSubject] = useAddDevicePageStore( (state) => [state.setState, state.nextStep, state.nextSubject], @@ -69,43 +73,58 @@ export const AddDeviceSetupMethodStep = () => { setPageState({ loading: isLoading }); }, [isLoading, setPageState]); + useEffectOnce(() => { + if (enterpriseSettings?.only_client_activation) { + setPageState({ method: AddDeviceMethod.DESKTOP }); + nextSubject.next(); + } + }); + return ( <> - - - } - linkText={localLL.remote.link()} - link={externalLink.defguardReleases} - selected={setupMethod === AddDeviceMethod.DESKTOP} - onSelect={() => { - if (setupMethod !== AddDeviceMethod.DESKTOP) { - setPageState({ method: AddDeviceMethod.DESKTOP }); - } - }} - /> - } - linkText={localLL.manual.link()} - link={externalLink.wireguard.download} - selected={setupMethod === AddDeviceMethod.MANUAL} - onSelect={() => { - if (setupMethod !== AddDeviceMethod.MANUAL) { - setPageState({ method: AddDeviceMethod.MANUAL }); - } - }} - /> - + {!enterpriseSettings?.only_client_activation ? ( + <> + + + } + linkText={localLL.remote.link()} + link={externalLink.defguardReleases} + selected={setupMethod === AddDeviceMethod.DESKTOP} + onSelect={() => { + if (setupMethod !== AddDeviceMethod.DESKTOP) { + setPageState({ method: AddDeviceMethod.DESKTOP }); + } + }} + /> + } + linkText={localLL.manual.link()} + link={externalLink.wireguard.download} + selected={setupMethod === AddDeviceMethod.MANUAL} + onSelect={() => { + if (setupMethod !== AddDeviceMethod.MANUAL) { + setPageState({ method: AddDeviceMethod.MANUAL }); + } + }} + /> + + + ) : ( +
+ +
+ )} ); }; diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/style.scss b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/style.scss index 8d6df3effb..2911287b78 100644 --- a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/style.scss +++ b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/style.scss @@ -28,4 +28,12 @@ } } } + + #spinner-box { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + min-height: 500px; + } } diff --git a/web/src/pages/addDevice/steps/AddDeviceTokenStep/AddDeviceTokenStep.tsx b/web/src/pages/addDevice/steps/AddDeviceTokenStep/AddDeviceTokenStep.tsx index 2da1830d6e..12d3f9305e 100644 --- a/web/src/pages/addDevice/steps/AddDeviceTokenStep/AddDeviceTokenStep.tsx +++ b/web/src/pages/addDevice/steps/AddDeviceTokenStep/AddDeviceTokenStep.tsx @@ -1,5 +1,6 @@ import './style.scss'; +import parse from 'html-react-parser'; import { isUndefined } from 'lodash-es'; import { ReactNode, useEffect, useMemo } from 'react'; import { useNavigate } from 'react-router'; @@ -10,6 +11,8 @@ import { ActionButton } from '../../../../shared/defguard-ui/components/Layout/A import { ActionButtonVariant } from '../../../../shared/defguard-ui/components/Layout/ActionButton/types'; import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; import { ExpandableCard } from '../../../../shared/defguard-ui/components/Layout/ExpandableCard/ExpandableCard'; +import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; +import { MessageBoxType } from '../../../../shared/defguard-ui/components/Layout/MessageBox/types'; import { useClipboard } from '../../../../shared/hooks/useClipboard'; import { useAddDevicePageStore } from '../../hooks/useAddDevicePageStore'; @@ -78,14 +81,20 @@ export const AddDeviceTokenStep = () => { }, [resetPage, nextSubject, navigate, userData]); return ( - -

{localLL.title()}

- -

{url}

-
- -

{token}

-
-
+ <> + + +

{localLL.title()}

+ +

{url}

+
+ +

{token}

+
+
+ ); }; diff --git a/web/src/pages/auth/AuthPage.tsx b/web/src/pages/auth/AuthPage.tsx index ea2c14633c..97015e7919 100644 --- a/web/src/pages/auth/AuthPage.tsx +++ b/web/src/pages/auth/AuthPage.tsx @@ -13,6 +13,7 @@ import useApi from '../../shared/hooks/useApi'; import { useToaster } from '../../shared/hooks/useToaster'; import { UserMFAMethod } from '../../shared/types'; import { RedirectPage } from '../redirect/RedirectPage'; +import { OpenIDCallback } from './Callback/Callback'; import { Login } from './Login/Login'; import { MFARoute } from './MFARoute/MFARoute'; import { useMFAStore } from './shared/hooks/useMFAStore'; @@ -48,6 +49,8 @@ export const AuthPage = () => { const setAppStore = useAppStore((state) => state.setState); + const enterpriseEnabled = useAppStore((state) => state.enterprise_status?.enabled); + const [params] = useSearchParams(); const redirectUrl = params.get('r'); @@ -152,6 +155,7 @@ export const AuthPage = () => { } /> } /> } /> + {enterpriseEnabled && } />} } />
diff --git a/web/src/pages/auth/Callback/Callback.tsx b/web/src/pages/auth/Callback/Callback.tsx new file mode 100644 index 0000000000..85ecfe3407 --- /dev/null +++ b/web/src/pages/auth/Callback/Callback.tsx @@ -0,0 +1,83 @@ +import './style.scss'; + +import { useMutation } from '@tanstack/react-query'; +import { AxiosError } from 'axios'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; + +import { useI18nContext } from '../../../i18n/i18n-react'; +import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; +import { LoaderSpinner } from '../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; +import { useAuthStore } from '../../../shared/hooks/store/useAuthStore'; +import useApi from '../../../shared/hooks/useApi'; +import { useToaster } from '../../../shared/hooks/useToaster'; +import { MutationKeys } from '../../../shared/mutations'; +import { CallbackData } from '../../../shared/types'; + +export const OpenIDCallback = () => { + const { + auth: { + openid: { callback }, + }, + } = useApi(); + const loginSubject = useAuthStore((state) => state.loginSubject); + const toaster = useToaster(); + const { LL } = useI18nContext(); + const [error, setError] = useState(null); + const navigate = useNavigate(); + + const callbackMutation = useMutation((data: CallbackData) => callback(data), { + mutationKey: [MutationKeys.OPENID_CALLBACK], + onSuccess: (data) => loginSubject.next(data), + onError: (error: AxiosError) => { + toaster.error(LL.messages.error()); + console.error(error); + }, + retry: false, + }); + + useEffect(() => { + if (window.location.hash && window.location.hash.length > 0) { + const hashFragment = window.location.hash.substring(1); + const params = new URLSearchParams(hashFragment); + + // check if error occured + const error = params.get('error'); + + if (error) { + setError(error); + toaster.error(LL.messages.error()); + return; + } + + const id_token = params.get('id_token'); + const state = params.get('state'); + + if (id_token && state) { + const data: CallbackData = { + id_token, + state, + }; + callbackMutation.mutate(data); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // FIXME: make it a bit more user friendly + return error ? ( +
+

+ {LL.loginPage.callback.error()}: {error} +

+
+ ) : ( + + ); +}; diff --git a/web/src/pages/auth/Callback/style.scss b/web/src/pages/auth/Callback/style.scss new file mode 100644 index 0000000000..763c165186 --- /dev/null +++ b/web/src/pages/auth/Callback/style.scss @@ -0,0 +1,5 @@ +.error-info { + display: flex; + flex-direction: column; + gap: 10px; +} diff --git a/web/src/pages/auth/Login/Login.tsx b/web/src/pages/auth/Login/Login.tsx index 4a9123c336..6425fd4f9e 100644 --- a/web/src/pages/auth/Login/Login.tsx +++ b/web/src/pages/auth/Login/Login.tsx @@ -1,7 +1,7 @@ import './style.scss'; import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import { useMemo } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; @@ -14,12 +14,17 @@ import { ButtonSize, ButtonStyleVariant, } from '../../../shared/defguard-ui/components/Layout/Button/types'; +import { LoaderSpinner } from '../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; +import { useAppStore } from '../../../shared/hooks/store/useAppStore'; import { useAuthStore } from '../../../shared/hooks/store/useAuthStore'; import useApi from '../../../shared/hooks/useApi'; +import { useToaster } from '../../../shared/hooks/useToaster'; import { MutationKeys } from '../../../shared/mutations'; import { patternSafeUsernameCharacters } from '../../../shared/patterns'; +import { QueryKeys } from '../../../shared/queries'; import { LoginData } from '../../../shared/types'; import { trimObjectStrings } from '../../../shared/utils/trimObjectStrings'; +import { OpenIdLoginButton } from './components/OidcButtons'; type Inputs = { username: string; @@ -28,6 +33,23 @@ type Inputs = { export const Login = () => { const { LL } = useI18nContext(); + const { + auth: { + login, + openid: { getOpenIdInfo: getOpenidInfo }, + }, + } = useApi(); + const toaster = useToaster(); + + const enterpriseEnabled = useAppStore((state) => state.enterprise_status?.enabled); + const { data: openIdInfo, isLoading: openIdLoading } = useQuery({ + enabled: enterpriseEnabled, + queryKey: [QueryKeys.FETCH_OPENID_INFO], + queryFn: getOpenidInfo, + refetchOnMount: true, + refetchOnWindowFocus: false, + retry: false, + }); const zodSchema = useMemo( () => @@ -46,10 +68,6 @@ export const Login = () => { [LL.form.error], ); - const { - auth: { login }, - } = useApi(); - const { handleSubmit, control, setError } = useForm({ resolver: zodResolver(zodSchema), mode: 'all', @@ -65,16 +83,30 @@ export const Login = () => { mutationKey: [MutationKeys.LOG_IN], onSuccess: (data) => loginSubject.next(data), onError: (error: AxiosError) => { - if (error.response && error.response.status === 401) { - setError( - 'password', - { - message: 'username or password is incorrect', - }, - { shouldFocus: true }, - ); + if (error.response) { + switch (error.response.status) { + case 401: { + setError( + 'password', + { + message: 'username or password is incorrect', + }, + { shouldFocus: true }, + ); + break; + } + case 429: { + toaster.error(LL.form.error.tooManyBadLoginAttempts()); + break; + } + default: { + console.error(error); + toaster.error(LL.messages.error()); + } + } } else { console.error(error); + toaster.error(LL.messages.error()); } }, }); @@ -87,32 +119,41 @@ export const Login = () => { return (
-

{LL.loginPage.pageTitle()}

-
- - -
); }; diff --git a/web/src/pages/auth/Login/components/OidcButtons.tsx b/web/src/pages/auth/Login/components/OidcButtons.tsx new file mode 100644 index 0000000000..d2e339c58c --- /dev/null +++ b/web/src/pages/auth/Login/components/OidcButtons.tsx @@ -0,0 +1,186 @@ +/* eslint-disable max-len */ +import './style.scss'; + +import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; +import { + ButtonSize, + ButtonStyleVariant, +} from '../../../../shared/defguard-ui/components/Layout/Button/types'; + +export const OpenIdLoginButton = ({ url }: { url: string }) => { + const { hostname } = new URL(url); + + if (hostname === 'accounts.google.com') { + return ; + } else if (hostname === 'login.microsoftonline.com') { + return ; + } else { + return ; + } +}; + +const GoogleButton = ({ url }: { url: string }) => { + return ( + + ); +}; + +const CustomButton = ({ url }: { url: string }) => { + return ( + + ); +}; diff --git a/web/src/pages/auth/Login/components/style.scss b/web/src/pages/auth/Login/components/style.scss new file mode 100644 index 0000000000..422c0fcec8 --- /dev/null +++ b/web/src/pages/auth/Login/components/style.scss @@ -0,0 +1,120 @@ +.gsi-material-button { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + -webkit-appearance: none; + background-color: WHITE; + background-image: none; + border: 1px solid #747775; + -webkit-border-radius: 4px; + border-radius: 4px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: #1f1f1f; + cursor: pointer; + font-family: 'Roboto', arial, sans-serif; + font-size: 14px; + height: 40px; + letter-spacing: 0.25px; + outline: none; + overflow: hidden; + padding: 0 12px; + position: relative; + text-align: center; + -webkit-transition: + background-color 0.218s, + border-color 0.218s, + box-shadow 0.218s; + transition: + background-color 0.218s, + border-color 0.218s, + box-shadow 0.218s; + vertical-align: middle; + white-space: nowrap; + width: auto; + max-width: 400px; + min-width: min-content; +} + +.gsi-material-button .gsi-material-button-icon { + height: 20px; + margin-right: 12px; + min-width: 20px; + width: 20px; +} + +.gsi-material-button .gsi-material-button-content-wrapper { + -webkit-align-items: center; + align-items: center; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: nowrap; + flex-wrap: nowrap; + height: 100%; + justify-content: space-between; + position: relative; + width: 100%; +} + +.gsi-material-button .gsi-material-button-contents { + -webkit-flex-grow: 1; + flex-grow: 1; + font-family: 'Roboto', arial, sans-serif; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; +} + +.gsi-material-button .gsi-material-button-state { + -webkit-transition: opacity 0.218s; + transition: opacity 0.218s; + bottom: 0; + left: 0; + opacity: 0; + position: absolute; + right: 0; + top: 0; +} + +.gsi-material-button:disabled { + cursor: default; + background-color: #ffffff61; + border-color: #1f1f1f1f; +} + +.gsi-material-button:disabled .gsi-material-button-contents { + opacity: 38%; +} + +.gsi-material-button:disabled .gsi-material-button-icon { + opacity: 38%; +} + +.gsi-material-button:not(:disabled):active .gsi-material-button-state, +.gsi-material-button:not(:disabled):focus .gsi-material-button-state { + background-color: #303030; + opacity: 12%; +} + +.gsi-material-button:not(:disabled):hover { + -webkit-box-shadow: + 0 1px 2px 0 rgba(60, 64, 67, 0.3), + 0 1px 3px 1px rgba(60, 64, 67, 0.15); + box-shadow: + 0 1px 2px 0 rgba(60, 64, 67, 0.3), + 0 1px 3px 1px rgba(60, 64, 67, 0.15); +} + +.gsi-material-button:not(:disabled):hover .gsi-material-button-state { + background-color: #303030; + opacity: 8%; +} + +.ms-button { + background-color: transparent; + border: none; + cursor: pointer; + padding: 0; +} diff --git a/web/src/pages/auth/Login/style.scss b/web/src/pages/auth/Login/style.scss index 46b746ad0e..a35ae9200c 100644 --- a/web/src/pages/auth/Login/style.scss +++ b/web/src/pages/auth/Login/style.scss @@ -36,6 +36,10 @@ margin-bottom: 5px; } + & > .btn { + margin-bottom: 10px; + } + & > * { width: 100%; } diff --git a/web/src/pages/auth/MFARoute/MFAEmail/MFAEmail.tsx b/web/src/pages/auth/MFARoute/MFAEmail/MFAEmail.tsx index 3bb1762bd3..63c9547e06 100644 --- a/web/src/pages/auth/MFARoute/MFAEmail/MFAEmail.tsx +++ b/web/src/pages/auth/MFARoute/MFAEmail/MFAEmail.tsx @@ -92,7 +92,7 @@ export const MFAEmail = () => { const handleValidSubmit: SubmitHandler = (data) => { const trimmed = trimObjectStrings(data); verifyMutate({ - code: Number.parseInt(trimmed.code), + code: String(trimmed.code), }); }; diff --git a/web/src/pages/auth/MFARoute/MFATOTPAuth/MFATOTPAuth.tsx b/web/src/pages/auth/MFARoute/MFATOTPAuth/MFATOTPAuth.tsx index 8b3b3ceef0..ee401ca167 100644 --- a/web/src/pages/auth/MFARoute/MFATOTPAuth/MFATOTPAuth.tsx +++ b/web/src/pages/auth/MFARoute/MFATOTPAuth/MFATOTPAuth.tsx @@ -65,7 +65,7 @@ export const MFATOTPAuth = () => { const handleValidSubmit: SubmitHandler = (values) => { const trimmed = trimObjectStrings(values); - mutate({ code: Number(trimmed.code) }); + mutate({ code: String(trimmed.code) }); }; useEffect(() => { diff --git a/web/src/pages/network/NetworkGateway/NetworkGateway.tsx b/web/src/pages/network/NetworkGateway/NetworkGateway.tsx index 994322485d..9dc55208df 100644 --- a/web/src/pages/network/NetworkGateway/NetworkGateway.tsx +++ b/web/src/pages/network/NetworkGateway/NetworkGateway.tsx @@ -1,13 +1,18 @@ import './style.scss'; import { useQuery } from '@tanstack/react-query'; -import parse from 'html-react-parser'; import { useCallback, useMemo } from 'react'; +import ReactMarkdown from 'react-markdown'; import { useI18nContext } from '../../../i18n/i18n-react'; import { GatewaysStatus } from '../../../shared/components/network/GatewaysStatus/GatewaysStatus'; import { ActionButton } from '../../../shared/defguard-ui/components/Layout/ActionButton/ActionButton'; import { ActionButtonVariant } from '../../../shared/defguard-ui/components/Layout/ActionButton/types'; +import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; +import { + ButtonSize, + ButtonStyleVariant, +} from '../../../shared/defguard-ui/components/Layout/Button/types'; import { ExpandableCard } from '../../../shared/defguard-ui/components/Layout/ExpandableCard/ExpandableCard'; import { MessageBox } from '../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; import useApi from '../../../shared/hooks/useApi'; @@ -38,6 +43,10 @@ export const NetworkGatewaySetup = () => { return `docker run -e DEFGUARD_TOKEN=${networkToken?.token} -e DEFGUARD_GRPC_URL=${networkToken?.grpc_url} --restart unless-stopped --network host --cap-add NET_ADMIN ghcr.io/defguard/gateway:latest`; }, [networkToken]); + const returnNetworkToken = useCallback(() => { + return `${networkToken?.token}`; + }, [networkToken]); + const getActions = useMemo( () => [ { ], [command, writeToClipboard], ); + + const getNetworkTokenActions = useMemo( + () => [ + { + writeToClipboard(returnNetworkToken()); + }} + />, + ], + [returnNetworkToken, writeToClipboard], + ); + + // TODO: consider a better way to redirect to the gateway releases page + const handleSubmit = () => { + window.location.href = 'https://github.com/DefGuard/gateway/releases'; + }; + return (
-
-

{LL.gatewaySetup.header()}

-
+
+

{LL.gatewaySetup.header.main()}

+ {/* {parse( + LL.gatewaySetup.messages.runCommand({ + setupGatewayDocs: externalLink.gitbook.setup.gateway, + }), + )} */} + + {LL.gatewaySetup.messages.runCommand({ + setupGatewayDocs: externalLink.gitbook.setup.gateway, + })} + +
- {parse( - networkToken - ? LL.gatewaySetup.messages.runCommand({ + + {networkToken + ? LL.gatewaySetup.messages.authToken({ setupGatewayDocs: externalLink.gitbook.setup.gateway, }) - : LL.gatewaySetup.messages.createNetwork(), - )} + : LL.gatewaySetup.messages.createNetwork()} + + + {networkToken && ( + <> + +

{returnNetworkToken()}

+
+ + )} +

{LL.gatewaySetup.header.dockerBasedGatewaySetup()}

+ + + {networkToken + ? LL.gatewaySetup.messages.dockerBasedGatewaySetup({ + setupGatewayDocs: externalLink.gitbook.setup.gateway, + }) + : LL.gatewaySetup.messages.createNetwork()} + {networkToken && ( <> @@ -76,6 +136,24 @@ export const NetworkGatewaySetup = () => { )} +

{LL.gatewaySetup.header.fromPackage()}

+ + + {LL.gatewaySetup.messages.fromPackage({ + setupGatewayDocs: externalLink.gitbook.setup.gateway, + })} + + +
); diff --git a/web/src/pages/network/NetworkGateway/style.scss b/web/src/pages/network/NetworkGateway/style.scss index 2572b19017..340a535c8e 100644 --- a/web/src/pages/network/NetworkGateway/style.scss +++ b/web/src/pages/network/NetworkGateway/style.scss @@ -1,11 +1,22 @@ #network-page { .gateway { + display: flex; + flex-direction: column; + row-gap: 20px; + + & > section.header-section { + h2, + p { + margin-bottom: 35px; + } + } + & > .message-box { - min-height: 80px; - margin-bottom: 2rem; + min-height: 15px; + margin-bottom: 20px; @include media-breakpoint-up(lg) { - margin-bottom: 4rem; + margin-bottom: 40px; } } @@ -24,10 +35,40 @@ @include small-text; line-height: 22px; + color: var(--text-body-secondary); + padding: 20px 0px; } } - margin-bottom: 3rem; + margin-bottom: 30px; + } + + h2 { + @include typography(app-body-1); + color: var(--text-body-primary); + text-align: center; + } + + h3 { + @include typography(modal-title); + + color: var(--text-body-primary); + } + + p { + & > a { + color: var(--text-body-secondary); + } + @include typography(app-modal-2); + color: var(--text-body-secondary); + } + + & > p { + text-align: center; + } + + & > .btn { + width: 100%; } } } diff --git a/web/src/pages/settings/SettingsPage.tsx b/web/src/pages/settings/SettingsPage.tsx index bf6b9dac9f..3bc0672a62 100644 --- a/web/src/pages/settings/SettingsPage.tsx +++ b/web/src/pages/settings/SettingsPage.tsx @@ -12,8 +12,10 @@ import { CardTabsData } from '../../shared/defguard-ui/components/Layout/CardTab import { LoaderSpinner } from '../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; import useApi from '../../shared/hooks/useApi'; import { QueryKeys } from '../../shared/queries'; +import { EnterpriseSettings } from './components/EnterpriseSettings/EnterpriseSettings'; import { GlobalSettings } from './components/GlobalSettings/GlobalSettings'; import { LdapSettings } from './components/LdapSettings/LdapSettings'; +import { OpenIdSettings } from './components/OpenIdSettings/OpenIdSettings'; import { SmtpSettings } from './components/SmtpSettings/SmtpSettings'; import { useSettingsPage } from './hooks/useSettingsPage'; @@ -21,6 +23,8 @@ const tabsContent: ReactNode[] = [ , , , + , + , ]; export const SettingsPage = () => { @@ -65,6 +69,18 @@ export const SettingsPage = () => { active: activeCard === 2, onClick: () => setActiveCard(2), }, + { + key: 3, + content: LL.settingsPage.tabs.openid(), + active: activeCard === 3, + onClick: () => setActiveCard(3), + }, + { + key: 4, + content: LL.settingsPage.tabs.enterprise(), + active: activeCard === 4, + onClick: () => setActiveCard(4), + }, ], [LL.settingsPage.tabs, activeCard], ); diff --git a/web/src/pages/settings/components/EnterpriseSettings/EnterpriseSettings.tsx b/web/src/pages/settings/components/EnterpriseSettings/EnterpriseSettings.tsx new file mode 100644 index 0000000000..af50d00569 --- /dev/null +++ b/web/src/pages/settings/components/EnterpriseSettings/EnterpriseSettings.tsx @@ -0,0 +1,35 @@ +import { useI18nContext } from '../../../../i18n/i18n-react'; +import { useAppStore } from '../../../../shared/hooks/store/useAppStore'; +import { EnterpriseForm } from './components/EnterpriseForm'; + +export const EnterpriseSettings = () => { + const enterpriseStatus = useAppStore((state) => state.enterprise_status); + const { LL } = useI18nContext(); + const localLL = LL.settingsPage.enterpriseOnly; + return ( + <> + {!enterpriseStatus?.enabled && ( +
+
+
+

{localLL.title()}

+ {/* If enterprise is disabled but we have some license info, the license probably expired */} + {enterpriseStatus?.license_info &&

{localLL.currentExpired()}

} +

+ {localLL.subtitle()}{' '} + + {localLL.website()} + + . +

+
+
+
+ )} +
+ +
+
+ + ); +}; diff --git a/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx b/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx new file mode 100644 index 0000000000..fac922a097 --- /dev/null +++ b/web/src/pages/settings/components/EnterpriseSettings/components/EnterpriseForm.tsx @@ -0,0 +1,94 @@ +import './styles.scss'; + +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { AxiosError } from 'axios'; +import parse from 'html-react-parser'; + +import { useI18nContext } from '../../../../../i18n/i18n-react'; +import { Card } from '../../../../../shared/defguard-ui/components/Layout/Card/Card'; +import { Helper } from '../../../../../shared/defguard-ui/components/Layout/Helper/Helper'; +import { LabeledCheckbox } from '../../../../../shared/defguard-ui/components/Layout/LabeledCheckbox/LabeledCheckbox'; +import { useAppStore } from '../../../../../shared/hooks/store/useAppStore'; +import useApi from '../../../../../shared/hooks/useApi'; +import { useToaster } from '../../../../../shared/hooks/useToaster'; +import { MutationKeys } from '../../../../../shared/mutations'; +import { QueryKeys } from '../../../../../shared/queries'; + +export const EnterpriseForm = () => { + const { LL } = useI18nContext(); + const toaster = useToaster(); + const { + settings: { patchEnterpriseSettings }, + } = useApi(); + + const settings = useAppStore((state) => state.enterprise_settings); + + const queryClient = useQueryClient(); + + const { mutate, isLoading } = useMutation( + [MutationKeys.EDIT_SETTINGS], + patchEnterpriseSettings, + { + onSuccess: () => { + queryClient.invalidateQueries([QueryKeys.FETCH_ENTERPRISE_SETTINGS]); + toaster.success(LL.settingsPage.messages.editSuccess()); + }, + onError: (err: AxiosError) => { + toaster.error(LL.messages.error()); + console.error(err); + }, + }, + ); + + if (!settings) return null; + + return ( +
+
+

{LL.settingsPage.enterprise.header()}

+ {parse(LL.settingsPage.enterprise.helper())} +
+ +
+ + mutate({ admin_device_management: !settings.admin_device_management }) + } + /> + + {parse(LL.settingsPage.enterprise.fields.deviceManagement.helper())} + +
+
+ + mutate({ only_client_activation: !settings.only_client_activation }) + } + /> + + {parse(LL.settingsPage.enterprise.fields.manualConfig.helper())} + +
+
+ + mutate({ disable_all_traffic: !settings.disable_all_traffic }) + } + /> + + {parse(LL.settingsPage.enterprise.fields.disableAllTraffic.helper())} + +
+
+
+ ); +}; diff --git a/web/src/pages/settings/components/EnterpriseSettings/components/styles.scss b/web/src/pages/settings/components/EnterpriseSettings/components/styles.scss new file mode 100644 index 0000000000..c2a9f0a228 --- /dev/null +++ b/web/src/pages/settings/components/EnterpriseSettings/components/styles.scss @@ -0,0 +1,19 @@ +@use '@scssutils' as *; + +#enterprise-settings { + & > .card { + display: flex; + flex-flow: column; + row-gap: 16px; + + @include media-breakpoint-up(lg) { + padding: 16px 15px; + } + + & > .checkbox-row { + display: flex; + align-items: center; + gap: 10px; + } + } +} diff --git a/web/src/pages/settings/components/GlobalSettings/GlobalSettings.tsx b/web/src/pages/settings/components/GlobalSettings/GlobalSettings.tsx index d641199739..6025d59c0b 100644 --- a/web/src/pages/settings/components/GlobalSettings/GlobalSettings.tsx +++ b/web/src/pages/settings/components/GlobalSettings/GlobalSettings.tsx @@ -1,4 +1,5 @@ import { BrandingSettings } from './components/BrandingSettings/BrandingSettings'; +import { LicenseSettings } from './components/LicenseSettings/LicenseSettings'; import { ModulesSettings } from './components/ModulesSettings/ModulesSettings'; import { Web3Settings } from './components/Web3Settings/Web3Settings'; @@ -9,6 +10,7 @@ export const GlobalSettings = () => (
+
diff --git a/web/src/pages/settings/components/GlobalSettings/components/BrandingSettings/BrandingSettings.tsx b/web/src/pages/settings/components/GlobalSettings/components/BrandingSettings/BrandingSettings.tsx index 98246d6e41..81e90852bc 100644 --- a/web/src/pages/settings/components/GlobalSettings/components/BrandingSettings/BrandingSettings.tsx +++ b/web/src/pages/settings/components/GlobalSettings/components/BrandingSettings/BrandingSettings.tsx @@ -3,7 +3,7 @@ import './styles.scss'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import parse from 'html-react-parser'; -import { useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { useBreakpoint } from 'use-breakpoint'; import { z } from 'zod'; @@ -117,16 +117,12 @@ export const BrandingSettings = () => { }; }, [settings?.instance_name, settings?.main_logo_url, settings?.nav_logo_url]); - const { control, handleSubmit, reset } = useForm({ + const { control, handleSubmit, setValue } = useForm({ defaultValues, mode: 'all', resolver: zodResolver(zodSchema), }); - useEffect(() => { - reset(); - }, [reset, defaultValues]); - const onSubmit: SubmitHandler = (submitted) => { mutate(mergeWithDefaults(submitted)); }; @@ -156,7 +152,12 @@ export const BrandingSettings = () => { icon={} styleVariant={ButtonStyleVariant.PRIMARY} loading={isLoading} - onClick={() => setDefaultBrandingMutation('1')} + onClick={() => { + setDefaultBrandingMutation('1'); + setValue('instance_name', defaultSettings.instance_name); + setValue('main_logo_url', ''); + setValue('nav_logo_url', ''); + }} />