diff --git a/.env b/.env new file mode 100644 index 0000000000..941bd7206b --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +NEXT_PUBLIC_BASE_PATH=/ui/v2/login +EMAIL_VERIFICATION=false +DEBUG=true +ZITADEL_API_URL=http://localhost:8080 +ZITADEL_SERVICE_USER_TOKEN_FILE=../../login-client.pat diff --git a/.env.prod b/.env.prod new file mode 100644 index 0000000000..454804b07c --- /dev/null +++ b/.env.prod @@ -0,0 +1 @@ +ZITADEL_SERVICE_USER_TOKEN_FILE=../../../../login-client.pat diff --git a/.env.test b/.env.test deleted file mode 100644 index c0344ebf0f..0000000000 --- a/.env.test +++ /dev/null @@ -1,6 +0,0 @@ -ZITADEL_API_URL=http://localhost:22222 -ZITADEL_SERVICE_USER_TOKEN="yolo" -EMAIL_VERIFICATION=true -DEBUG=true -PORT=3001 -NEXT_PUBLIC_BASE_PATH=/ui/v2/login diff --git a/.env.test-integration b/.env.test-integration new file mode 100644 index 0000000000..136bf21f25 --- /dev/null +++ b/.env.test-integration @@ -0,0 +1,2 @@ +API_MOCK_STUBS_HOST=${DEVCONTAINER_LOGIN_API_MOCK_HOST:-localhost} +API_MOCK_STUBS_URL=http://${API_MOCK_STUBS_HOST}:22220/v1/stubs diff --git a/.env.test-integration-run-login b/.env.test-integration-run-login new file mode 100644 index 0000000000..8eb5c685d0 --- /dev/null +++ b/.env.test-integration-run-login @@ -0,0 +1,4 @@ +ZITADEL_API_URL=http://${DEVCONTAINER_LOGIN_API_MOCK_HOST:-localhost}:22222 +ZITADEL_SERVICE_USER_TOKEN=none +ZITADEL_SERVICE_USER_TOKEN_FILE="" +PORT=3001 \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index d704a7f0c3..bbca76e6c9 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -12,6 +12,12 @@ module.exports = { varsIgnorePattern: "^_" , }], "no-undef": "off", + "no-restricted-imports": ["error", { + "paths": [{ + "name": "next/image", + "message": "Use of next/image is forbidden. Use regular elements instead." + }] + }], }, parserOptions: { ecmaVersion: "latest", diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml deleted file mode 100644 index 2764c1a365..0000000000 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ /dev/null @@ -1,63 +0,0 @@ -name: 🐛 Bug Report -description: "Create a bug report to help us improve ZITADEL Typescript Library." -title: "[Bug]: " -labels: ["bug"] -body: -- type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! -- type: checkboxes - id: preflight - attributes: - label: Preflight Checklist - options: - - label: - I could not find a solution in the documentation, the existing issues or discussions - required: true - - label: - I have joined the [ZITADEL chat](https://zitadel.com/chat) - validations: - required: true -- type: input - id: version - attributes: - label: Version - description: Which version of ZITADEL Typescript Library are you using. -- type: textarea - id: impact - attributes: - label: Describe the problem caused by this bug - description: A clear and concise description of the problem you have and what the bug is. - validations: - required: true -- type: textarea - id: reproduce - attributes: - label: To reproduce - description: Steps to reproduce the behaviour - placeholder: | - Steps to reproduce the behavior: - 1. ... - validations: - required: true -- type: textarea - id: screenshots - attributes: - label: Screenshots - description: If applicable, add screenshots to help explain your problem. -- type: textarea - id: expected - attributes: - label: Expected behavior - description: A clear and concise description of what you expected to happen. -- type: textarea - id: config - attributes: - label: Relevant Configuration - description: Add any relevant configurations that could help us. Make sure to redact any sensitive information. -- type: textarea - id: additional - attributes: - label: Additional Context - description: Please add any other infos that could be useful. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 7e690b9344..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,4 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: 💬 ZITADEL Community Chat - url: https://zitadel.com/chat diff --git a/.github/ISSUE_TEMPLATE/docs.yaml b/.github/ISSUE_TEMPLATE/docs.yaml deleted file mode 100644 index 04c1c0cdb1..0000000000 --- a/.github/ISSUE_TEMPLATE/docs.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: 📄 Documentation -description: Create an issue for missing or wrong documentation. -labels: ["docs"] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this issue. - - type: checkboxes - id: preflight - attributes: - label: Preflight Checklist - options: - - label: - I could not find a solution in the existing issues, docs, nor discussions - required: true - - label: - I have joined the [ZITADEL chat](https://zitadel.com/chat) - - type: textarea - id: docs - attributes: - label: Describe the docs your are missing or that are wrong - placeholder: As a [type of user], I want [some goal] so that [some reason]. - validations: - required: true - - type: textarea - id: additional - attributes: - label: Additional Context - description: Please add any other infos that could be useful. diff --git a/.github/ISSUE_TEMPLATE/improvement.yaml b/.github/ISSUE_TEMPLATE/improvement.yaml deleted file mode 100644 index cfe79d407b..0000000000 --- a/.github/ISSUE_TEMPLATE/improvement.yaml +++ /dev/null @@ -1,54 +0,0 @@ -name: 🛠️ Improvement -description: "Create an new issue for an improvment in ZITADEL" -labels: ["improvement"] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this improvement request - - type: checkboxes - id: preflight - attributes: - label: Preflight Checklist - options: - - label: - I could not find a solution in the existing issues, docs, nor discussions - required: true - - label: - I have joined the [ZITADEL chat](https://zitadel.com/chat) - - type: textarea - id: problem - attributes: - label: Describe your problem - description: Please describe your problem this improvement is supposed to solve. - placeholder: Describe the problem you have - validations: - required: true - - type: textarea - id: solution - attributes: - label: Describe your ideal solution - description: Which solution do you propose? - placeholder: As a [type of user], I want [some goal] so that [some reason]. - validations: - required: true - - type: input - id: version - attributes: - label: Version - description: Which version of the typescript library are you using. - - type: dropdown - id: environment - attributes: - label: Environment - description: How do you use ZITADEL? - options: - - ZITADEL Cloud - - Self-hosted - validations: - required: true - - type: textarea - id: additional - attributes: - label: Additional Context - description: Please add any other infos that could be useful. diff --git a/.github/ISSUE_TEMPLATE/proposal.yaml b/.github/ISSUE_TEMPLATE/proposal.yaml deleted file mode 100644 index cd9ff66972..0000000000 --- a/.github/ISSUE_TEMPLATE/proposal.yaml +++ /dev/null @@ -1,54 +0,0 @@ -name: 💡 Proposal / Feature request -description: "Create an issue for a feature request/proposal." -labels: ["enhancement"] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this proposal / feature reqeust - - type: checkboxes - id: preflight - attributes: - label: Preflight Checklist - options: - - label: - I could not find a solution in the existing issues, docs, nor discussions - required: true - - label: - I have joined the [ZITADEL chat](https://zitadel.com/chat) - - type: textarea - id: problem - attributes: - label: Describe your problem - description: Please describe your problem this proposal / feature is supposed to solve. - placeholder: Describe the problem you have. - validations: - required: true - - type: textarea - id: solution - attributes: - label: Describe your ideal solution - description: Which solution do you propose? - placeholder: As a [type of user], I want [some goal] so that [some reason]. - validations: - required: true - - type: input - id: version - attributes: - label: Version - description: Which version of the Typescript Library are you using. - - type: dropdown - id: environment - attributes: - label: Environment - description: How do you use ZITADEL? - options: - - ZITADEL Cloud - - Self-hosted - validations: - required: true - - type: textarea - id: additional - attributes: - label: Additional Context - description: Please add any other infos that could be useful. diff --git a/.github/custom-i18n.png b/.github/custom-i18n.png deleted file mode 100644 index 2306e62f87..0000000000 Binary files a/.github/custom-i18n.png and /dev/null differ diff --git a/.github/dependabot.example.yml b/.github/dependabot.example.yml deleted file mode 100644 index 8f3906c179..0000000000 --- a/.github/dependabot.example.yml +++ /dev/null @@ -1,21 +0,0 @@ -version: 2 -updates: - - package-ecosystem: github-actions - directory: '/' - open-pull-requests-limit: 1 - schedule: - interval: 'daily' - - - package-ecosystem: npm - directory: '/' - open-pull-requests-limit: 3 - schedule: - interval: daily - groups: - prod: - dependency-type: production - dev: - dependency-type: development - ignore: - - dependency-name: "eslint" - versions: [ "9.x" ] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 138d4919af..0000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,13 +0,0 @@ -### Definition of Ready - -- [ ] I am happy with the code -- [ ] Short description of the feature/issue is added in the pr description -- [ ] PR is linked to the corresponding user story -- [ ] Acceptance criteria are met -- [ ] All open todos and follow ups are defined in a new ticket and justified -- [ ] Deviations from the acceptance criteria and design are agreed with the PO and documented. -- [ ] Vitest unit tests ensure that components produce expected outputs on different inputs. -- [ ] Cypress integration tests ensure that login app pages work as expected on good and bad user inputs, ZITADEL responses or IDP redirects. The ZITADEL API is mocked, IDP redirects are simulated. -- [ ] Playwright acceptances tests ensure that the happy paths of common user journeys work as expected. The ZITADEL API is not mocked but IDP redirects are simulated. -- [ ] No debug or dead code -- [ ] My code has no repetitions diff --git a/.github/workflows/close_pr.yml b/.github/workflows/close_pr.yml deleted file mode 100644 index 90f92eff55..0000000000 --- a/.github/workflows/close_pr.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Auto-close PRs and guide to correct repo - -on: - workflow_dispatch: - pull_request_target: - types: [opened] - -jobs: - auto-close: - runs-on: ubuntu-latest - if: github.repository_id == '622995060' && github.ref_name != 'mirror-zitadel-repo' - steps: - - name: Comment and close PR - uses: actions/github-script@v7 - with: - script: | - const message = ` - 👋 **Thanks for your contribution @${{ github.event.pull_request.user.login }}!** - - This repository \`${{ github.repository }}\` is a read-only mirror of the git subtree at [\`zitadel/zitadel/login\`](https://github.com/zitadel/zitadel). - Therefore, we close this pull request automatically. - - Your changes are not lost. Submitting them to the main repository is easy: - 1. [Fork zitadel/zitadel](https://github.com/zitadel/zitadel/fork) - 2. Clone your Zitadel fork \`git clone https://github.com//zitadel.git\` - 3. Change directory to your Zitadel forks root. - 4. Pull your changes into the Zitadel fork by running \`make login_pull LOGIN_REMOTE_URL=https://github.com//typescript.git LOGIN_REMOTE_BRANCH=\`. - 5. Push your changes and [open a pull request to zitadel/zitadel](https://github.com/zitadel/zitadel/compare) - `.trim(); - await github.rest.issues.createComment({ - ...context.repo, - issue_number: context.issue.number, - body: message - }); - await github.rest.pulls.update({ - ...context.repo, - pull_number: context.issue.number, - state: "closed" - }); diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml deleted file mode 100644 index ff12b8fe04..0000000000 --- a/.github/workflows/issues.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Add new issues to product management project - -on: - issues: - types: - - opened - -jobs: - add-to-project: - name: Add issue and community pr to project - runs-on: ubuntu-latest - if: github.repository_id == '622995060' - steps: - - name: add issue - uses: actions/add-to-project@v1.0.2 - if: ${{ github.event_name == 'issues' }} - with: - # You can target a repository in a different organization - # to the issue - project-url: https://github.com/orgs/zitadel/projects/2 - github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} - - uses: tspascoal/get-user-teams-membership@v3 - id: checkUserMember - if: github.actor != 'dependabot[bot]' - with: - username: ${{ github.actor }} - GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }} - - name: add pr - uses: actions/add-to-project@v1.0.2 - if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'engineers')}} - with: - # You can target a repository in a different organization - # to the issue - project-url: https://github.com/orgs/zitadel/projects/2 - github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} - - uses: actions-ecosystem/action-add-labels@v1.1.3 - if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'staff')}} - with: - github_token: ${{ secrets.ADD_TO_PROJECT_PAT }} - labels: | - os-contribution diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 2508627d1b..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Release - -on: - push: - branches: - - main - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - release: - runs-on: ubuntu-latest - if: github.repository_id != '622995060' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Install dependencies - run: pnpm install - - - name: Create Release Pull Request - uses: changesets/action@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 7b4721dbee..0000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Quality -on: - pull_request: - workflow_dispatch: - inputs: - ignore-run-cache: - description: 'Whether to ignore the run cache' - required: false - default: true - ref-tag: - description: 'overwrite the DOCKER_METADATA_OUTPUT_VERSION environment variable used by the make file' - required: false - default: '' -jobs: - quality: - name: Ensure Quality - if: github.event_name == 'workflow_dispatch' || - (github.event_name == 'pull_request' && github.repository_id != '622995060') - runs-on: ubuntu-22.04 - timeout-minutes: 30 - permissions: - contents: read # We only need read access to the repository contents - actions: write # We need write access to the actions cache - env: - CACHE_DIR: /tmp/login-run-caches - # Only run this job on workflow_dispatch or pushes to forks - steps: - - uses: actions/checkout@v4 - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ghcr.io/zitadel/login - tags: | - type=raw,value=latest,enable={{is_default_branch}} - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - - name: Set up Buildx - uses: docker/setup-buildx-action@v3 - # Only with correctly restored build cache layers, the run caches work as expected. - # To restore docker build layer caches, extend the docker-bake.hcl to use the cache-from and cache-to options. - # https://docs.docker.com/build/ci/github-actions/cache/ - # Alternatively, you can use a self-hosted runner or a third-party builder that restores build layer caches out-of-the-box, like https://depot.dev/ - - name: Restore Run Caches - uses: actions/cache/restore@v4 - id: run-caches-restore - with: - path: ${{ env.CACHE_DIR }} - key: ${{ runner.os }}-login-run-caches-${{github.ref_name}}-${{ github.sha }}-${{github.run_attempt}} - restore-keys: | - ${{ runner.os }}-login-run-caches-${{github.ref_name}}-${{ github.sha }}- - ${{ runner.os }}-login-run-caches-${{github.ref_name}}- - ${{ runner.os }}-login-run-caches- - - run: make login_quality - env: - IGNORE_RUN_CACHE: ${{ github.event.inputs.ignore-run-cache == 'true' }} - DOCKER_METADATA_OUTPUT_VERSION: ${{ github.event.inputs.ref-tag || env.DOCKER_METADATA_OUTPUT_VERSION || steps.meta.outputs.version }} - - name: Save Run Caches - uses: actions/cache/save@v4 - with: - path: ${{ env.CACHE_DIR }} - key: ${{ steps.run-caches-restore.outputs.cache-primary-key }} - if: always() diff --git a/.gitignore b/.gitignore index e433d14fb8..97c0f2a74b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ cypress .DS_Store node_modules -.turbo *.log .next dist diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index ac3f129652..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -- Demonstrating empathy and kindness toward other people -- Being respectful of differing opinions, viewpoints, and experiences -- Giving and gracefully accepting constructive feedback -- Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -- Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -- The use of sexualized language or imagery, and sexual attention or - advances of any kind -- Trolling, insulting or derogatory comments, and personal or political attacks -- Public or private harassment -- Publishing others' private information, such as a physical or email - address, without their explicit permission -- Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -legal@zitadel.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series -of actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or -permanent ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within -the community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. - -Community Impact Guidelines were inspired by [Mozilla's code of conduct -enforcement ladder](https://github.com/mozilla/diversity). - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 50ca3172d1..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,218 +0,0 @@ -# Contributing - -:attention: In this CONTRIBUTING.md you read about contributing to this very repository. -If you want to develop your own login UI, please refer [to the README.md](./README.md). - -## Introduction - -Thank you for your interest about how to contribute! - -:attention: If you notice a possible **security vulnerability**, please don't hesitate to disclose any concern by contacting [security@zitadel.com](mailto:security@zitadel.com). -You don't have to be perfectly sure about the nature of the vulnerability. -We will give them a high priority and figure them out. - -We also appreciate all your other ideas, thoughts and feedback and will take care of them as soon as possible. -We love to discuss in an open space using [GitHub issues](https://github.com/zitadel/typescript/issues), -[GitHub discussions in the core repo](https://github.com/zitadel/zitadel/discussions) -or in our [chat on Discord](https://zitadel.com/chat). -For private discussions, -you have [more contact options on our Website](https://zitadel.com/contact). - -## Pull Requests - -The repository zitadel/typescript is a read-only mirror of the git subtree at zitadel/zitadel/login. -To submit changes, please open a Pull Request [in the zitadel/zitadel repository](https://github.com/zitadel/zitadel/compare). - -If you already made changes based on the zitadel/typescript repository, these changes are not lost. -Submitting them to the main repository is easy: - -1. [Fork zitadel/zitadel](https://github.com/zitadel/zitadel/fork) -1. Clone your Zitadel fork git clone https://github.com//zitadel.git -1. Change directory to your Zitadel forks root. -1. Pull your changes into the Zitadel fork by running make login_pull LOGIN_REMOTE_URL=https://github.com//typescript.git LOGIN_REMOTE_BRANCH=. -1. Push your changes and [open a pull request to zitadel/zitadel](https://github.com/zitadel/zitadel/compare) - -Please consider the following guidelines when creating a pull request. - -- The latest changes are always in `main`, so please make your pull request against that branch. -- pull requests should be raised for any change -- Pull requests need approval of a Zitadel core engineer @zitadel/engineers before merging -- We use ESLint/Prettier for linting/formatting, so please run `pnpm lint:fix` before committing to make resolving conflicts easier (VSCode users, check out [this ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) and [this Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) to fix lint and formatting issues in development) -- If you add new functionality, please provide the corresponding documentation as well and make it part of the pull request - -### Setting up local environment - -```sh -# Install dependencies. Developing requires Node.js v20 -pnpm install - -# Generate gRPC stubs -pnpm generate - -# Start a local development server for the login and manually configure apps/login/.env.local -pnpm dev -``` - -The application is now available at `http://localhost:3000` - -Configure apps/login/.env.local to target the Zitadel instance of your choice. -The login app live-reloads on changes, so you can start developing right away. - -### Developing Against A Local Latest Zitadel Release - -The following command uses Docker to run a local Zitadel instance and the login application in live-reloading dev mode. -Additionally, it runs a Traefik reverse proxy that exposes the login with a self-signed certificate at https://127.0.0.1.sslip.io -127.0.0.1.sslip.io is a special domain that resolves to your localhost, so it's safe to allow your browser to proceed with loading the page. - -```sh -# Install dependencies. Developing requires Node.js v20 -pnpm install - -# Generate gRPC stubs -pnpm generate - -# Start a local development server and have apps/login/.env.test.local configured for you to target the local Zitadel instance. -pnpm dev:local -``` - -Log in at https://127.0.0.1.sslip.io/ui/v2/login/loginname and use the following credentials: -**Loginname**: *zitadel-admin@zitadel.127.0.0.1.sslip.io* -**Password**: _Password1!_. - -The login app live-reloads on changes, so you can start developing right away. - -### Developing Against A Locally Compiled Zitadel - -To develop against a locally compiled version of Zitadel, you need to build the Zitadel docker image first. -Clone the [Zitadel repository](https://github.com/zitadel/zitadel.git) and run the following command from its root: - -```sh -# This compiles a Zitadel binary if it does not exist at ./zitadel already and copies it into a Docker image. -# If you want to recompile the binary, run `make compile` first -make login_dev -``` - -Open another terminal session at zitadel/zitadel/login and run the following commands to start the dev server. - -```bash -# Install dependencies. Developing requires Node.js v20 -pnpm install - -# Start a local development server and have apps/login/.env.test.local configured for you to target the local Zitadel instance. -NODE_ENV=test pnpm dev -``` - -Log in at https://127.0.0.1.sslip.io/ui/v2/login/loginname and use the following credentials: -**Loginname**: *zitadel-admin@zitadel.127.0.0.1.sslip.io* -**Password**: _Password1!_. - -The login app live-reloads on changes, so you can start developing right away. - -### Quality Assurance - -Use `make` commands to test the quality of your code against a production build without installing any dependencies besides Docker. -Using `make` commands, you can reproduce and debug the CI pipelines locally. - -```sh -# Reproduce the whole CI pipeline in docker -make login_quality -# Show other options with make -make help -``` - -Use `pnpm` commands to run the tests in dev mode with live reloading and debugging capabilities. - -#### Linting and formatting - -Check the formatting and linting of the code in docker - -```sh -make login_lint -``` - -Check the linting of the code using pnpm - -```sh -pnpm lint -pnpm format -``` - -Fix the linting of your code - -```sh -pnpm lint:fix -pnpm format:fix -``` - -#### Running Unit Tests - -Run the tests in docker - -```sh -make login_test_unit -``` - -Run unit tests with live-reloading - -```sh -pnpm test:unit -``` - -#### Running Integration Tests - -Run the test in docker - -```sh -make login_test_integration -``` - -Alternatively, run a live-reloading development server with an interactive Cypress test suite. -First, set up your local test environment. - -```sh -# Install dependencies. Developing requires Node.js v20 -pnpm install - -# Generate gRPC stubs -pnpm generate - -# Start a local development server and use apps/login/.env.test to use the locally mocked Zitadel API. -pnpm test:integration:setup -``` - -Now, in another terminal session, open the interactive Cypress integration test suite. - -```sh -pnpm test:integration open -``` - -Show more options with Cypress - -```sh -pnpm test:integration help -``` - -#### Running Acceptance Tests - -To run the tests in docker against the latest release of Zitadel, use the following command: - -:warning: The acceptance tests are not reliable at the moment :construction: - -```sh -make login_test_acceptance -``` - -Alternatively, run can use a live-reloading development server with an interactive Playwright test suite. -Set up your local environment by running the commands either for [developing against a local latest Zitadel release](latest) or for [developing against a locally compiled Zitadel](compiled). - -Now, in another terminal session, open the interactive Playwright acceptance test suite. - -```sh -pnpm test:acceptance open -``` - -Show more options with Playwright - -```sh -pnpm test:acceptance help -``` diff --git a/Dockerfile b/Dockerfile index 3207ada8e7..b95e3ab47c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,37 +1,18 @@ -FROM node:20-alpine AS base - -FROM base AS build -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable && COREPACK_ENABLE_DOWNLOAD_PROMPT=0 corepack prepare pnpm@10.13.1 --activate && \ - apk update && \ - rm -rf /var/cache/apk/* +FROM node:22-alpine WORKDIR /app -COPY pnpm-lock.yaml ./ -RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch --frozen-lockfile -COPY package.json ./ -RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile -COPY . . -RUN pnpm build:login:standalone - -FROM scratch AS build-out -COPY --from=build /app/.next/standalone / -COPY --from=build /app/.next/static /.next/static -COPY public public - -FROM base AS login-standalone -WORKDIR /runtime RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs # If /.env-file/.env is mounted into the container, its variables are made available to the server before it starts up. RUN mkdir -p /.env-file && touch /.env-file/.env && chown -R nextjs:nodejs /.env-file -COPY --chown=nextjs:nodejs ./scripts/ ./ -COPY --chown=nextjs:nodejs --from=build-out / ./ + +COPY --chown=nextjs:nodejs .next/standalone ./ + USER nextjs ENV HOSTNAME="0.0.0.0" \ - NEXT_PUBLIC_BASE_PATH="/ui/v2/login" \ - PORT=3000 + PORT="3000" \ + NODE_ENV="production" + # TODO: Check healthy, not ready HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD ["/bin/sh", "-c", "node /runtime/healthcheck.js http://localhost:${PORT}/ui/v2/login/healthy"] -ENTRYPOINT ["/runtime/entrypoint.sh"] + CMD ["/bin/sh", "-c", "node /app/healthcheck.js http://localhost:${PORT}/ui/v2/login/healthy"] +ENTRYPOINT ["/app/entrypoint.sh", "node", "apps/login/server.js" ] diff --git a/Dockerfile.dockerignore b/Dockerfile.dockerignore deleted file mode 100644 index d65fd69947..0000000000 --- a/Dockerfile.dockerignore +++ /dev/null @@ -1,22 +0,0 @@ -* - -!constants -!scripts -!src -!public -!locales -!next.config.mjs -!next-env-vars.d.ts -!next-env.d.ts -!tailwind.config.mjs -!postcss.config.cjs -!tsconfig.json -!package.json -!pnpm-lock.yaml - -**/*.md -**/*.png -**/node_modules -**/.turbo -**/*.test.ts -**/*.test.tsx \ No newline at end of file diff --git a/acceptance/oidcrp/main.go b/acceptance/oidcrp/main.go index 72ae5f57e9..83efa12a71 100644 --- a/acceptance/oidcrp/main.go +++ b/acceptance/oidcrp/main.go @@ -20,7 +20,6 @@ import ( "github.com/google/uuid" "github.com/sirupsen/logrus" - "github.com/zitadel/logging" "github.com/zitadel/oidc/v3/pkg/client/rp" httphelper "github.com/zitadel/oidc/v3/pkg/http" diff --git a/constants/csp.js b/constants/csp.js index 5cc1e254f3..dc3dc1e05a 100644 --- a/constants/csp.js +++ b/constants/csp.js @@ -1,2 +1,6 @@ +const ZITADEL_DOMAIN = process.env.ZITADEL_API_URL + ? new URL(process.env.ZITADEL_API_URL).hostname + : '*.zitadel.cloud'; + export const DEFAULT_CSP = - "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://vercel.com;"; + `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; child-src; style-src 'self' 'unsafe-inline'; font-src 'self'; object-src 'none'; img-src 'self' https://${ZITADEL_DOMAIN};`; diff --git a/cypress.config.ts b/cypress.config.ts index f5bae5fe90..ca01336fbd 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,5 +1,4 @@ import { defineConfig } from "cypress"; - export default defineConfig({ reporter: "list", video: true, @@ -7,11 +6,12 @@ export default defineConfig({ runMode: 2 }, e2e: { - baseUrl: process.env.LOGIN_BASE_URL || "http://localhost:3001/ui/v2/login", + baseUrl: `http://localhost:3001${process.env.NEXT_PUBLIC_BASE_PATH || ""}`, specPattern: "integration/integration/**/*.cy.{js,jsx,ts,tsx}", supportFile: "integration/support/e2e.{js,jsx,ts,tsx}", - setupNodeEvents(on, config) { - // implement node event listeners here - }, + pageLoadTimeout: 120_0000, + env: { + API_MOCK_STUBS_URL: process.env.API_MOCK_STUBS_URL || "http://localhost:22220/v1/stubs" + } }, }); diff --git a/docker-bake-release.hcl b/docker-bake-release.hcl deleted file mode 100644 index 51e1c194f6..0000000000 --- a/docker-bake-release.hcl +++ /dev/null @@ -1,3 +0,0 @@ -target "release" { - platforms = ["linux/amd64", "linux/arm64"] -} diff --git a/docker-bake.hcl b/docker-bake.hcl deleted file mode 100644 index e09d1176e0..0000000000 --- a/docker-bake.hcl +++ /dev/null @@ -1,25 +0,0 @@ -variable "LOGIN_TAG" { - default = "zitadel-login:local" -} - -group "default" { - targets = ["login-standalone"] -} - -# The release target is overwritten in docker-bake-release.hcl -# It makes sure the image is built for multiple platforms. -# By default the platforms property is empty, so images are only built for the current bake runtime platform. -target "release" {} - -target "docker-metadata-action" { - # In the pipeline, this target is overwritten by the docker metadata action. - tags = ["${LOGIN_TAG}"] -} - -# We run integration and acceptance tests against the next standalone server for docker. -target "login-standalone" { - inherits = [ - "docker-metadata-action", - "release", - ] -} diff --git a/integration/core-mock/Dockerfile b/integration/api-mock/Dockerfile similarity index 86% rename from integration/core-mock/Dockerfile rename to integration/api-mock/Dockerfile index ce2465480f..84e7d1ca88 100644 --- a/integration/core-mock/Dockerfile +++ b/integration/api-mock/Dockerfile @@ -3,9 +3,7 @@ RUN buf export https://github.com/envoyproxy/protoc-gen-validate.git --path vali buf export https://github.com/grpc-ecosystem/grpc-gateway.git --path protoc-gen-openapiv2 --output /proto && \ buf export https://github.com/googleapis/googleapis.git --path google/api/annotations.proto --path google/api/http.proto --path google/api/field_behavior.proto --output /proto -FROM bufbuild/buf:1.54.0 AS zitadel-protos -RUN buf export https://github.com/zitadel/zitadel.git --path ./proto/zitadel --output /zitadel - + FROM golang:1.20.5-alpine3.18 AS mock-zitadel RUN go install github.com/eliobischof/grpc-mock/cmd/grpc-mock@01b09f60db1b501178af59bed03b2c22661df48c diff --git a/integration/core-mock/initial-stubs/zitadel.settings.v2.SettingsService.json b/integration/api-mock/initial-stubs/zitadel.settings.v2.SettingsService.json similarity index 100% rename from integration/core-mock/initial-stubs/zitadel.settings.v2.SettingsService.json rename to integration/api-mock/initial-stubs/zitadel.settings.v2.SettingsService.json diff --git a/integration/core-mock/mocked-services.cfg b/integration/api-mock/mocked-services.cfg similarity index 100% rename from integration/core-mock/mocked-services.cfg rename to integration/api-mock/mocked-services.cfg diff --git a/integration/api-mock/project.json b/integration/api-mock/project.json new file mode 100644 index 0000000000..0a5d92ff3b --- /dev/null +++ b/integration/api-mock/project.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../../../node_modules/nx/schemas/project-schema.json", + "name": "@zitadel/login-api-mock", + "targets": { + "build": { + "description": "Builds the API mock server", + "command": "nx run @zitadel/devcontainer:compose build login-api-mock" + }, + "serve": { + "description": "Starts the API mock server", + "continuous": true, + "command": "nx run @zitadel/devcontainer:compose up --force-recreate --renew-anon-volumes login-api-mock" + }, + "down": { + "description": "Stops the API mock server", + "command": "nx run @zitadel/devcontainer:compose down login-api-mock" + } + } +} \ No newline at end of file diff --git a/integration/integration/invite.cy.ts b/integration/integration/invite.cy.ts index 4a370be549..344950db43 100644 --- a/integration/integration/invite.cy.ts +++ b/integration/integration/invite.cy.ts @@ -89,7 +89,7 @@ describe("verify invite", () => { }); }); - it.only("shows authenticators after successful invite verification", () => { + it("shows authenticators after successful invite verification", () => { stub("zitadel.user.v2.UserService", "VerifyInviteCode"); cy.visit("/verify?userId=221394658884845598&code=abc&invite=true"); diff --git a/integration/support/e2e.ts b/integration/support/e2e.ts index 58056c973e..75641d0239 100644 --- a/integration/support/e2e.ts +++ b/integration/support/e2e.ts @@ -1,4 +1,24 @@ -const url = Cypress.env("CORE_MOCK_STUBS_URL") || "http://localhost:22220/v1/stubs"; +// Handle React 19 SSR hydration errors that don't affect functionality +Cypress.on('uncaught:exception', (err, runnable) => { + // React error #419 is specifically about SSR Suspense boundary issues + // This doesn't affect the actual functionality, just the SSR/CSR transition + if (err.message.includes('Minified React error #419')) { + console.warn('Cypress: Suppressed React SSR error #419 (Suspense boundary issue):', err.message); + return false; + } + // Other hydration mismatches that are common with React 19 + Next.js 15 + if (err.message.includes('server could not finish this Suspense boundary') || + err.message.includes('Switched to client rendering') || + err.message.includes('Hydration failed') || + err.message.includes('Text content does not match server-rendered HTML')) { + console.warn('Cypress: Suppressed React hydration error (non-functional):', err.message); + return false; + } + // Let other errors fail the test as they should + return true; +}); + +const url = Cypress.env("API_MOCK_STUBS_URL"); function removeStub(service: string, method: string) { return cy.request({ diff --git a/locales/de.json b/locales/de.json index 7b2a507fe4..55a6355120 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1,6 +1,7 @@ { "common": { - "back": "Zurück" + "back": "Zurück", + "title": "Anmelden mit Zitadel" }, "accounts": { "title": "Konten", @@ -26,6 +27,12 @@ "description": "Geben Sie Ihre Anmeldedaten ein.", "register": "Neuen Benutzer registrieren", "submit": "Weiter", + "labels": { + "loginname": "Loginname", + "username": "Benutzername", + "usernameOrPhoneNumber": "Benutzername oder Telefonnummer", + "usernameOrEmail": "Benutzername oder E-Mail" + }, "required": { "loginName": "Dieses Feld ist erforderlich" } @@ -36,6 +43,9 @@ "description": "Geben Sie Ihr Passwort ein.", "resetPassword": "Passwort zurücksetzen", "submit": "Weiter", + "labels": { + "password": "Passwort" + }, "required": { "password": "Dieses Feld ist erforderlich" } @@ -47,6 +57,11 @@ "noCodeReceived": "Keinen Code erhalten?", "resend": "Erneut senden", "submit": "Weiter", + "labels": { + "code": "Code", + "newPassword": "Neues Passwort", + "confirmPassword": "Neues Passwort wiederholen" + }, "required": { "code": "Dieses Feld ist erforderlich", "newPassword": "Bitte geben Sie ein Passwort ein!", @@ -57,6 +72,10 @@ "title": "Passwort ändern", "description": "Legen Sie das Passwort für Ihr Konto fest", "submit": "Weiter", + "labels": { + "newPassword": "Neues Passwort", + "confirmPassword": "Neues Passwort wiederholen" + }, "required": { "newPassword": "Bitte geben Sie ein neues Passwort ein!", "confirmPassword": "Dieses Feld ist erforderlich" @@ -100,9 +119,11 @@ "ldap": { "title": "LDAP Login", "description": "Geben Sie Ihre LDAP-Anmeldedaten ein.", - "username": "Benutzername", - "password": "Passwort", "submit": "Weiter", + "labels": { + "username": "Benutzername", + "password": "Passwort" + }, "required": { "username": "Dieses Feld ist erforderlich", "password": "Dieses Feld ist erforderlich" @@ -129,6 +150,9 @@ "noCodeReceived": "Keinen Code erhalten?", "resendCode": "Code erneut senden", "submit": "Weiter", + "labels": { + "code": "Code" + }, "required": { "code": "Dieses Feld ist erforderlich" } @@ -140,6 +164,9 @@ "emailDescription": "Geben Sie Ihre E-Mail-Adresse ein, um einen Code per E-Mail zu erhalten.", "totpRegisterDescription": "Scannen Sie den QR-Code oder navigieren Sie manuell zur URL.", "submit": "Weiter", + "labels": { + "code": "Code" + }, "required": { "code": "Dieses Feld ist erforderlich" } @@ -177,7 +204,7 @@ "register": { "methods": { "passkey": "Passkey", - "password": "Password" + "password": "Passwort" }, "disabled": { "title": "Registrierung deaktiviert", @@ -195,16 +222,24 @@ "termsOfService": "Nutzungsbedingungen", "privacyPolicy": "Datenschutzrichtlinie", "submit": "Weiter", - "orUseIDP": "oder verwenden Sie einen Identitätsanbieter", "password": { "title": "Passwort festlegen", "description": "Legen Sie das Passwort für Ihr Konto fest", "submit": "Weiter", + "labels": { + "password": "Passwort", + "confirmPassword": "Neues Passwort wiederholen" + }, "required": { "password": "Bitte geben Sie ein Passwort ein!", "confirmPassword": "Dieses Feld ist erforderlich" } }, + "labels": { + "firstname": "Vorname", + "lastname": "Nachname", + "email": "E-Mail" + }, "required": { "firstname": "Dieses Feld ist erforderlich", "lastname": "Dieses Feld ist erforderlich", @@ -246,6 +281,9 @@ "resendCode": "Code erneut senden", "codeSent": "Ein Code wurde gerade an Ihre E-Mail-Adresse gesendet.", "submit": "Weiter", + "labels": { + "code": "Code" + }, "required": { "code": "Dieses Feld ist erforderlich" } @@ -263,6 +301,9 @@ "title": "Gerätecode", "description": "Geben Sie den Code ein.", "submit": "Weiter", + "labels": { + "code": "Code" + }, "required": { "code": "Dieses Feld ist erforderlich" } diff --git a/locales/en.json b/locales/en.json index e1b7e4e82c..8d8c11ec3b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1,6 +1,7 @@ { "common": { - "back": "Back" + "back": "Back", + "title": "Login with Zitadel" }, "accounts": { "title": "Accounts", @@ -26,6 +27,12 @@ "description": "Enter your login data.", "register": "Register new user", "submit": "Continue", + "labels": { + "loginname": "Loginname", + "username": "Username", + "usernameOrPhoneNumber": "Username or phone number", + "usernameOrEmail": "Username or email" + }, "required": { "loginName": "This field is required" } @@ -36,6 +43,9 @@ "description": "Enter your password.", "resetPassword": "Reset Password", "submit": "Continue", + "labels": { + "password": "Password" + }, "required": { "password": "This field is required" } @@ -47,6 +57,11 @@ "noCodeReceived": "Didn't receive a code?", "resend": "Resend code", "submit": "Continue", + "labels": { + "code": "Code", + "newPassword": "New Password", + "confirmPassword": "Confirm Password" + }, "required": { "code": "This field is required", "newPassword": "You have to provide a password!", @@ -57,6 +72,10 @@ "title": "Change Password", "description": "Set the password for your account", "submit": "Continue", + "labels": { + "newPassword": "New Password", + "confirmPassword": "Confirm Password" + }, "required": { "newPassword": "You have to provide a new password!", "confirmPassword": "This field is required" @@ -100,9 +119,11 @@ "ldap": { "title": "LDAP Login", "description": "Enter your LDAP credentials.", - "username": "Username", - "password": "Password", "submit": "Continue", + "labels": { + "username": "Username", + "password": "Password" + }, "required": { "username": "This field is required", "password": "This field is required" @@ -129,6 +150,9 @@ "noCodeReceived": "Didn't receive a code?", "resendCode": "Resend code", "submit": "Continue", + "labels": { + "code": "Code" + }, "required": { "code": "This field is required" } @@ -140,6 +164,9 @@ "emailDescription": "Enter your email address to receive a code via email.", "totpRegisterDescription": "Scan the QR Code or navigate to the URL manually.", "submit": "Continue", + "labels": { + "code": "Code" + }, "required": { "code": "This field is required" } @@ -195,16 +222,24 @@ "termsOfService": "Terms of Service", "privacyPolicy": "Privacy Policy", "submit": "Continue", - "orUseIDP": "or use an Identity Provider", "password": { "title": "Set Password", "description": "Set the password for your account", "submit": "Continue", + "labels": { + "password": "Password", + "confirmPassword": "Confirm Password" + }, "required": { "password": "You have to provide a password!", "confirmPassword": "This field is required" } }, + "labels": { + "firstname": "First name", + "lastname": "Last name", + "email": "E-mail" + }, "required": { "firstname": "This field is required", "lastname": "This field is required", @@ -246,6 +281,9 @@ "resendCode": "Resend code", "codeSent": "A code has just been sent to your email address.", "submit": "Continue", + "labels": { + "code": "Code" + }, "required": { "code": "This field is required" } @@ -263,6 +301,9 @@ "title": "Device code", "description": "Enter the code displayed on your app or device.", "submit": "Continue", + "labels": { + "code": "Code" + }, "required": { "code": "This field is required" } diff --git a/locales/es.json b/locales/es.json index b1c63583a7..82f1e9335d 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1,6 +1,7 @@ { "common": { - "back": "Atrás" + "back": "Atrás", + "title": "Iniciar sesión con Zitadel" }, "accounts": { "title": "Cuentas", @@ -26,6 +27,12 @@ "description": "Introduce tus datos de acceso.", "register": "Registrar nuevo usuario", "submit": "Continuar", + "labels": { + "loginname": "Nombre de inicio de sesión", + "username": "Nombre de usuario", + "usernameOrPhoneNumber": "Nombre de usuario o número de teléfono", + "usernameOrEmail": "Nombre de usuario o correo electrónico" + }, "required": { "loginName": "Este campo es obligatorio" } @@ -36,6 +43,9 @@ "description": "Introduce tu contraseña.", "resetPassword": "Restablecer contraseña", "submit": "Continuar", + "labels": { + "password": "Contraseña" + }, "required": { "password": "Este campo es obligatorio" } @@ -47,6 +57,11 @@ "noCodeReceived": "¿No recibiste un código?", "resend": "Reenviar código", "submit": "Continuar", + "labels": { + "code": "Código", + "newPassword": "Nueva contraseña", + "confirmPassword": "Confirmar contraseña" + }, "required": { "code": "Este campo es obligatorio", "newPassword": "¡Debes proporcionar una contraseña!", @@ -57,6 +72,10 @@ "title": "Cambiar Contraseña", "description": "Establece la contraseña para tu cuenta", "submit": "Continuar", + "labels": { + "newPassword": "Nueva contraseña", + "confirmPassword": "Confirmar contraseña" + }, "required": { "newPassword": "¡Debes proporcionar una nueva contraseña!", "confirmPassword": "Este campo es obligatorio" @@ -100,9 +119,11 @@ "ldap": { "title": "Iniciar sesión con LDAP", "description": "Introduce tus credenciales LDAP.", - "username": "Nombre de usuario", - "password": "Contraseña", "submit": "Continuar", + "labels": { + "username": "Nombre de usuario", + "password": "Contraseña" + }, "required": { "username": "Este campo es obligatorio", "password": "Este campo es obligatorio" @@ -129,6 +150,9 @@ "noCodeReceived": "¿No recibiste un código?", "resendCode": "Reenviar código", "submit": "Continuar", + "labels": { + "code": "Código" + }, "required": { "code": "Este campo es obligatorio" } @@ -140,6 +164,9 @@ "emailDescription": "Introduce tu dirección de correo electrónico para recibir un código por correo electrónico.", "totpRegisterDescription": "Escanea el código QR o navega manualmente a la URL.", "submit": "Continuar", + "labels": { + "code": "Código" + }, "required": { "code": "Este campo es obligatorio" } @@ -195,16 +222,24 @@ "termsOfService": "Términos de Servicio", "privacyPolicy": "Política de Privacidad", "submit": "Continuar", - "orUseIDP": "o usa un Proveedor de Identidad", "password": { "title": "Establecer Contraseña", "description": "Establece la contraseña para tu cuenta", "submit": "Continuar", + "labels": { + "password": "Contraseña", + "confirmPassword": "Confirmar contraseña" + }, "required": { "password": "¡Debes proporcionar una contraseña!", "confirmPassword": "Este campo es obligatorio" } }, + "labels": { + "firstname": "Nombre", + "lastname": "Apellidos", + "email": "Correo electrónico" + }, "required": { "firstname": "Este campo es obligatorio", "lastname": "Este campo es obligatorio", @@ -246,6 +281,9 @@ "resendCode": "Reenviar código", "codeSent": "Se ha enviado un código a tu dirección de correo electrónico.", "submit": "Continuar", + "labels": { + "code": "Código" + }, "required": { "code": "Este campo es obligatorio" } @@ -263,6 +301,9 @@ "title": "Código del dispositivo", "description": "Introduce el código.", "submit": "Continuar", + "labels": { + "code": "Código" + }, "required": { "code": "Este campo es obligatorio" } diff --git a/locales/it.json b/locales/it.json index a71b48ed04..815efb5a35 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,6 +1,7 @@ { "common": { - "back": "Indietro" + "back": "Indietro", + "title": "Accedi con Zitadel" }, "accounts": { "title": "Account", @@ -26,6 +27,12 @@ "description": "Inserisci i tuoi dati di accesso.", "register": "Registrati come nuovo utente", "submit": "Continua", + "labels": { + "loginname": "Nome di accesso", + "username": "Nome utente", + "usernameOrPhoneNumber": "Nome utente o numero di telefono", + "usernameOrEmail": "Nome utente o e-mail" + }, "required": { "loginName": "Questo campo è obbligatorio" } @@ -36,6 +43,9 @@ "description": "Inserisci la tua password.", "resetPassword": "Reimposta Password", "submit": "Continua", + "labels": { + "password": "Password" + }, "required": { "password": "Questo campo è obbligatorio" } @@ -47,6 +57,11 @@ "noCodeReceived": "Non hai ricevuto un codice?", "resend": "Invia di nuovo", "submit": "Continua", + "labels": { + "code": "Codice", + "newPassword": "Nuova password", + "confirmPassword": "Conferma password" + }, "required": { "code": "Questo campo è obbligatorio", "newPassword": "Devi fornire una password!", @@ -57,6 +72,10 @@ "title": "Cambia Password", "description": "Imposta la password per il tuo account", "submit": "Continua", + "labels": { + "newPassword": "Nuova password", + "confirmPassword": "Conferma password" + }, "required": { "newPassword": "Devi fornire una nuova password!", "confirmPassword": "Questo campo è obbligatorio" @@ -100,9 +119,11 @@ "ldap": { "title": "Accedi con LDAP", "description": "Inserisci le tue credenziali LDAP.", - "username": "Nome utente", - "password": "Password", "submit": "Continua", + "labels": { + "username": "Nome utente", + "password": "Password" + }, "required": { "username": "Questo campo è obbligatorio", "password": "Questo campo è obbligatorio" @@ -129,6 +150,9 @@ "noCodeReceived": "Non hai ricevuto un codice?", "resendCode": "Invia di nuovo il codice", "submit": "Continua", + "labels": { + "code": "Codice" + }, "required": { "code": "Questo campo è obbligatorio" } @@ -140,6 +164,9 @@ "emailDescription": "Inserisci il tuo indirizzo email per ricevere un codice via email.", "totpRegisterDescription": "Scansiona il codice QR o naviga manualmente all'URL.", "submit": "Continua", + "labels": { + "code": "Codice" + }, "required": { "code": "Questo campo è obbligatorio" } @@ -195,16 +222,24 @@ "termsOfService": "Termini di Servizio", "privacyPolicy": "Informativa sulla Privacy", "submit": "Continua", - "orUseIDP": "o usa un Identity Provider", "password": { "title": "Imposta Password", "description": "Imposta la password per il tuo account", "submit": "Continua", + "labels": { + "password": "Password", + "confirmPassword": "Conferma password" + }, "required": { "password": "Devi fornire una password!", "confirmPassword": "Questo campo è obbligatorio" } }, + "labels": { + "firstname": "Nome", + "lastname": "Cognome", + "email": "E-mail" + }, "required": { "firstname": "Questo campo è obbligatorio", "lastname": "Questo campo è obbligatorio", @@ -246,6 +281,9 @@ "resendCode": "Invia di nuovo il codice", "codeSent": "Un codice è stato appena inviato al tuo indirizzo email.", "submit": "Continua", + "labels": { + "code": "Codice" + }, "required": { "code": "Questo campo è obbligatorio" } @@ -263,6 +301,9 @@ "title": "Codice dispositivo", "description": "Inserisci il codice.", "submit": "Continua", + "labels": { + "code": "Codice" + }, "required": { "code": "Questo campo è obbligatorio" } diff --git a/locales/pl.json b/locales/pl.json index 3e8562a220..ae81ba7f39 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1,6 +1,7 @@ { "common": { - "back": "Powrót" + "back": "Powrót", + "title": "Zaloguj się za pomocą Zitadel" }, "accounts": { "title": "Konta", @@ -26,6 +27,12 @@ "description": "Wprowadź dane logowania.", "register": "Zarejestruj nowego użytkownika", "submit": "Kontynuuj", + "labels": { + "loginname": "Login", + "username": "Nazwa użytkownika", + "usernameOrPhoneNumber": "Nazwa użytkownika lub numer telefonu", + "usernameOrEmail": "Nazwa użytkownika lub e-mail" + }, "required": { "loginName": "To pole jest wymagane" } @@ -36,6 +43,9 @@ "description": "Wprowadź swoje hasło.", "resetPassword": "Zresetuj hasło", "submit": "Kontynuuj", + "labels": { + "password": "Hasło" + }, "required": { "password": "To pole jest wymagane" } @@ -47,6 +57,11 @@ "noCodeReceived": "Nie otrzymałeś kodu?", "resend": "Wyślij kod ponownie", "submit": "Kontynuuj", + "labels": { + "code": "Kod", + "newPassword": "Nowe hasło", + "confirmPassword": "Potwierdź nowe hasło" + }, "required": { "code": "To pole jest wymagane", "newPassword": "Musisz podać hasło!", @@ -57,6 +72,10 @@ "title": "Zmień hasło", "description": "Ustaw nowe hasło dla swojego konta", "submit": "Kontynuuj", + "labels": { + "newPassword": "Nowe hasło", + "confirmPassword": "Potwierdź nowe hasło" + }, "required": { "newPassword": "Musisz podać nowe hasło!", "confirmPassword": "To pole jest wymagane" @@ -100,9 +119,11 @@ "ldap": { "title": "Zaloguj się przez LDAP", "description": "Wprowadź swoje dane logowania LDAP.", - "username": "Nazwa użytkownika", - "password": "Hasło", "submit": "Kontynuuj", + "labels": { + "username": "Nazwa użytkownika", + "password": "Hasło" + }, "required": { "username": "To pole jest wymagane", "password": "To pole jest wymagane" @@ -129,6 +150,9 @@ "noCodeReceived": "Nie otrzymałeś kodu?", "resendCode": "Wyślij kod ponownie", "submit": "Kontynuuj", + "labels": { + "code": "Kod" + }, "required": { "code": "To pole jest wymagane" } @@ -140,6 +164,9 @@ "emailDescription": "Wprowadź swój adres e-mail, aby otrzymać kod e-mailem.", "totpRegisterDescription": "Zeskanuj kod QR lub otwórz adres URL ręcznie.", "submit": "Kontynuuj", + "labels": { + "code": "Kod" + }, "required": { "code": "To pole jest wymagane" } @@ -195,16 +222,24 @@ "termsOfService": "Regulamin", "privacyPolicy": "Polityka prywatności", "submit": "Kontynuuj", - "orUseIDP": "lub użyj dostawcy tożsamości", "password": { "title": "Ustaw hasło", "description": "Ustaw hasło dla swojego konta", "submit": "Kontynuuj", + "labels": { + "password": "Hasło", + "confirmPassword": "Potwierdź hasło" + }, "required": { "password": "Musisz podać hasło!", "confirmPassword": "To pole jest wymagane" } }, + "labels": { + "firstname": "Imię", + "lastname": "Nazwisko", + "email": "E-mail" + }, "required": { "firstname": "To pole jest wymagane", "lastname": "To pole jest wymagane", @@ -246,6 +281,9 @@ "resendCode": "Wyślij kod ponownie", "codeSent": "Kod został właśnie wysłany na twój adres e-mail.", "submit": "Kontynuuj", + "labels": { + "code": "Kod" + }, "required": { "code": "To pole jest wymagane" } @@ -263,6 +301,9 @@ "title": "Kod urządzenia", "description": "Wprowadź kod.", "submit": "Kontynuuj", + "labels": { + "code": "Kod" + }, "required": { "code": "To pole jest wymagane" } diff --git a/locales/ru.json b/locales/ru.json index 6ba2917e16..f5e91066db 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1,6 +1,7 @@ { "common": { - "back": "Назад" + "back": "Назад", + "title": "Войти с Zitadel" }, "accounts": { "title": "Аккаунты", @@ -26,6 +27,12 @@ "description": "Введите свои данные для входа.", "register": "Зарегистрировать нового пользователя", "submit": "Продолжить", + "labels": { + "loginname": "Логин", + "username": "Имя пользователя", + "usernameOrPhoneNumber": "Имя пользователя или номер телефона", + "usernameOrEmail": "Имя пользователя или электронная почта" + }, "required": { "loginName": "Это поле обязательно для заполнения" } @@ -36,6 +43,9 @@ "description": "Введите ваш пароль.", "resetPassword": "Сбросить пароль", "submit": "Продолжить", + "labels": { + "password": "Пароль" + }, "required": { "password": "Это поле обязательно для заполнения" } @@ -47,6 +57,11 @@ "noCodeReceived": "Не получили код?", "resend": "Отправить код повторно", "submit": "Продолжить", + "labels": { + "code": "Код", + "newPassword": "Новый пароль", + "confirmPassword": "Подтвердите пароль" + }, "required": { "code": "Это поле обязательно для заполнения", "newPassword": "Вы должны указать пароль!", @@ -57,6 +72,10 @@ "title": "Изменить пароль", "description": "Установите пароль для вашего аккаунта", "submit": "Продолжить", + "labels": { + "newPassword": "Новый пароль", + "confirmPassword": "Подтвердите пароль" + }, "required": { "newPassword": "Вы должны указать новый пароль!", "confirmPassword": "Это поле обязательно для заполнения" @@ -100,9 +119,11 @@ "ldap": { "title": "Войти через LDAP", "description": "Введите ваши учетные данные LDAP.", - "username": "Имя пользователя", - "password": "Пароль", "submit": "Продолжить", + "labels": { + "username": "Имя пользователя", + "password": "Пароль" + }, "required": { "username": "Это поле обязательно для заполнения", "password": "Это поле обязательно для заполнения" @@ -129,6 +150,9 @@ "noCodeReceived": "Не получили код?", "resendCode": "Отправить код повторно", "submit": "Продолжить", + "labels": { + "code": "Код" + }, "required": { "code": "Это поле обязательно для заполнения" } @@ -140,6 +164,9 @@ "emailDescription": "Введите email для получения кода.", "totpRegisterDescription": "Отсканируйте QR-код или перейдите по ссылке вручную.", "submit": "Продолжить", + "labels": { + "code": "Код" + }, "required": { "code": "Это поле обязательно для заполнения" } @@ -195,16 +222,24 @@ "termsOfService": "Условия использования", "privacyPolicy": "Политика конфиденциальности", "submit": "Продолжить", - "orUseIDP": "или используйте Identity Provider", "password": { "title": "Установить пароль", "description": "Установите пароль для вашего аккаунта", "submit": "Продолжить", + "labels": { + "password": "Пароль", + "confirmPassword": "Подтвердите пароль" + }, "required": { "password": "Вы должны указать пароль!", "confirmPassword": "Это поле обязательно для заполнения" } }, + "labels": { + "firstname": "Имя", + "lastname": "Фамилия", + "email": "Электронная почта" + }, "required": { "firstname": "Это поле обязательно для заполнения", "lastname": "Это поле обязательно для заполнения", @@ -246,6 +281,9 @@ "resendCode": "Отправить код повторно", "codeSent": "Код отправлен на ваш email.", "submit": "Продолжить", + "labels": { + "code": "Код" + }, "required": { "code": "Это поле обязательно для заполнения" } @@ -263,6 +301,9 @@ "title": "Код устройства", "description": "Введите код.", "submit": "Продолжить", + "labels": { + "code": "Код" + }, "required": { "code": "Это поле обязательно для заполнения" } diff --git a/locales/zh.json b/locales/zh.json index fe5f2d1867..3ec2ec31e9 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -1,6 +1,7 @@ { "common": { - "back": "返回" + "back": "返回", + "title": "使用 Zitadel 登录" }, "accounts": { "title": "账户", @@ -26,6 +27,12 @@ "description": "请输入您的登录信息。", "register": "注册新用户", "submit": "继续", + "labels": { + "loginname": "登录名", + "username": "用户名", + "usernameOrPhoneNumber": "用户名或电话号码", + "usernameOrEmail": "用户名或电子邮箱" + }, "required": { "loginName": "此字段为必填项" } @@ -36,6 +43,9 @@ "description": "请输入您的密码。", "resetPassword": "重置密码", "submit": "继续", + "labels": { + "password": "密码" + }, "required": { "password": "此字段为必填项" } @@ -47,6 +57,11 @@ "noCodeReceived": "没有收到验证码?", "resend": "重发验证码", "submit": "继续", + "labels": { + "code": "验证码", + "newPassword": "新密码", + "confirmPassword": "确认密码" + }, "required": { "code": "此字段为必填项", "newPassword": "必须提供密码!", @@ -57,6 +72,10 @@ "title": "更改密码", "description": "为您的账户设置密码", "submit": "继续", + "labels": { + "newPassword": "新密码", + "confirmPassword": "确认密码" + }, "required": { "newPassword": "必须提供新密码!", "confirmPassword": "此字段为必填项" @@ -100,9 +119,11 @@ "ldap": { "title": "使用 LDAP 登录", "description": "请输入您的 LDAP 凭据。", - "username": "用户名", - "password": "密码", "submit": "继续", + "labels": { + "username": "用户名", + "password": "密码" + }, "required": { "username": "此字段为必填项", "password": "此字段为必填项" @@ -129,6 +150,9 @@ "noCodeReceived": "没有收到验证码?", "resendCode": "重发验证码", "submit": "继续", + "labels": { + "code": "验证码" + }, "required": { "code": "此字段为必填项" } @@ -140,6 +164,9 @@ "emailDescription": "输入您的电子邮箱地址以接收电子邮件验证码。", "totpRegisterDescription": "扫描二维码或手动导航到URL。", "submit": "继续", + "labels": { + "code": "验证码" + }, "required": { "code": "此字段为必填项" } @@ -195,16 +222,24 @@ "termsOfService": "服务条款", "privacyPolicy": "隐私政策", "submit": "继续", - "orUseIDP": "或使用身份提供者", "password": { "title": "设置密码", "description": "为您的账户设置密码", "submit": "继续", + "labels": { + "password": "密码", + "confirmPassword": "确认密码" + }, "required": { "password": "必须提供密码!", "confirmPassword": "此字段为必填项" } }, + "labels": { + "firstname": "名字", + "lastname": "姓氏", + "email": "电子邮箱" + }, "required": { "firstname": "此字段为必填项", "lastname": "此字段为必填项", @@ -246,6 +281,9 @@ "resendCode": "重发验证码", "codeSent": "刚刚发送了一封包含验证码的电子邮件。", "submit": "继续", + "labels": { + "code": "验证码" + }, "required": { "code": "此字段为必填项" } @@ -263,6 +301,9 @@ "title": "设备代码", "description": "输入代码。", "submit": "继续", + "labels": { + "code": "验证码" + }, "required": { "code": "此字段为必填项" } diff --git a/next-env-vars.d.ts b/next-env-vars.d.ts index e7f8289ad4..1d8467a672 100644 --- a/next-env-vars.d.ts +++ b/next-env-vars.d.ts @@ -16,6 +16,9 @@ declare namespace NodeJS { /** * The service user token + * If ZITADEL_SERVICE_USER_TOKEN is set, its value is used. + * If ZITADEL_SERVICE_USER_TOKEN is not set but ZITADEL_SERVICE_USER_TOKEN_FILE is set, the application blocks until the file is created. + * As soon as the file exists, its content is read and ZITADEL_SERVICE_USER_TOKEN is set. */ ZITADEL_SERVICE_USER_TOKEN: string; diff --git a/next.config.mjs b/next.config.mjs index b84f11a230..c0aab041cc 100755 --- a/next.config.mjs +++ b/next.config.mjs @@ -3,8 +3,6 @@ import { DEFAULT_CSP } from "./constants/csp.js"; const withNextIntl = createNextIntlPlugin(); -/** @type {import('next').NextConfig} */ - const secureHeaders = [ { key: "Strict-Transport-Security", @@ -33,43 +31,26 @@ const secureHeaders = [ { key: "X-Frame-Options", value: "deny" }, ]; -const imageRemotePatterns = [ - { - protocol: "http", - hostname: "localhost", - port: "8080", - pathname: "/**", - }, - { - protocol: "https", - hostname: "*.zitadel.*", - port: "", - pathname: "/**", - }, -]; - -if (process.env.ZITADEL_API_URL) { - imageRemotePatterns.push({ - protocol: "https", - hostname: process.env.ZITADEL_API_URL?.replace("https://", "") || "", - port: "", - pathname: "/**", - }); -} - +/** @type {import('next').NextConfig} */ const nextConfig = { basePath: process.env.NEXT_PUBLIC_BASE_PATH, output: process.env.NEXT_OUTPUT_MODE || undefined, - reactStrictMode: true, // Recommended for the `pages` directory, default in `app`. + reactStrictMode: true, experimental: { dynamicIO: true, - }, - images: { - remotePatterns: imageRemotePatterns, + // Add React 19 compatibility optimizations + optimizePackageImports: ['@radix-ui/react-tooltip', '@heroicons/react'], }, eslint: { ignoreDuringBuilds: true, }, + // Improve SSR stability - not actually needed for React 19 SSR issues + // onDemandEntries: { + // maxInactiveAge: 25 * 1000, + // pagesBufferLength: 2, + // }, + // Better error handling for production builds + poweredByHeader: false, async headers() { return [ { diff --git a/package.json b/package.json index 7176647722..0c7c7e8dc8 100644 --- a/package.json +++ b/package.json @@ -4,21 +4,16 @@ "private": true, "type": "module", "scripts": { - "dev": "next dev", - "build": "next build", - "build:login:standalone": "NEXT_PUBLIC_BASE_PATH=/ui/v2/login NEXT_OUTPUT_MODE=standalone next build", - "start": "next start", - "lint": "pnpm run '/^lint:check:.*$/'", - "lint:check:next": "next lint", - "lint:check:prettier": "prettier --check .", - "lint:fix": "prettier --write .", - "test:unit": "vitest --run", + "build": "NEXT_OUTPUT_MODE=standalone next build && cp -r public scripts/* .next/standalone/ && cp -r .next/static .next/standalone/apps/login/.next/ && rm .next/standalone/apps/login/.env", + "build-vercel": "next build", + "dev": "HOSTNAME=127.0.0.1 ./scripts/entrypoint.sh next dev", + "prod": "cd ./.next/standalone && HOSTNAME=127.0.0.1 ./entrypoint.sh node apps/login/server.js", + "lint-check-next": "next lint", + "lint-check-prettier": "prettier --check .", + "lint-fix": "prettier --write .", + "test-unit": "vitest --run", "lint-staged": "lint-staged", - "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next", - "test:integration:login": "wait-on --simultaneous 1 http://localhost:3001/ui/v2/login/verify?userId=221394658884845598&code=abc && cypress run", - "test:acceptance": "dotenv -e ../login/.env.test.local playwright", - "test:acceptance:setup": "cd ../.. && make login_test_acceptance_setup_env && NODE_ENV=test turbo run test:acceptance:setup:dev", - "test:acceptance:setup:dev": "cd ../.. && make login_test_acceptance_setup_dev" + "clean": "rm -rf node_modules .next cypress" }, "pnpm": { "overrides": { @@ -36,9 +31,6 @@ "@heroicons/react": "2.1.3", "@radix-ui/react-tooltip": "^1.2.7", "@tailwindcss/forms": "0.5.7", - "@vercel/analytics": "^1.2.2", - "@zitadel/client": "latest", - "@zitadel/proto": "latest", "clsx": "1.2.1", "copy-to-clipboard": "^3.3.3", "deepmerge": "^4.3.1", @@ -75,11 +67,13 @@ "@typescript-eslint/parser": "^7.0.0", "@vercel/git-hooks": "1.0.0", "@vitejs/plugin-react": "^4.4.1", + "@zitadel/client": "workspace:*", + "@zitadel/proto": "workspace:*", "autoprefixer": "10.4.21", "concurrently": "^9.1.2", - "cypress": "^14.5.2", + "cypress": "^14.5.4", "dotenv-cli": "^8.0.0", - "env-cmd": "^10.0.0", + "env-cmd": "^10.1.0", "eslint": "^8.57.0", "eslint-config-next": "15.4.0-canary.86", "eslint-config-prettier": "^9.1.0", @@ -99,7 +93,6 @@ "ts-proto": "^2.7.0", "typescript": "^5.8.3", "vite-tsconfig-paths": "^5.1.4", - "vitest": "^2.0.0", - "wait-on": "^7.2.0" + "vitest": "^2.0.0" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index e63fa35ee3..0000000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,9145 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -overrides: - form-data@>=4.0.0 <4.0.4: '>=4.0.4' - -importers: - - .: - dependencies: - '@headlessui/react': - specifier: ^2.1.9 - version: 2.2.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@heroicons/react': - specifier: 2.1.3 - version: 2.1.3(react@19.1.0) - '@radix-ui/react-tooltip': - specifier: ^1.2.7 - version: 1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tailwindcss/forms': - specifier: 0.5.7 - version: 0.5.7(tailwindcss@3.4.14) - '@vercel/analytics': - specifier: ^1.2.2 - version: 1.5.0(next@15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0))(react@19.1.0) - '@zitadel/client': - specifier: latest - version: 1.3.1 - '@zitadel/proto': - specifier: latest - version: 1.3.1 - clsx: - specifier: 1.2.1 - version: 1.2.1 - copy-to-clipboard: - specifier: ^3.3.3 - version: 3.3.3 - deepmerge: - specifier: ^4.3.1 - version: 4.3.1 - lucide-react: - specifier: 0.469.0 - version: 0.469.0(react@19.1.0) - moment: - specifier: ^2.29.4 - version: 2.30.1 - next: - specifier: 15.4.0-canary.86 - version: 15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0) - next-intl: - specifier: ^3.25.1 - version: 3.26.5(next@15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0))(react@19.1.0) - next-themes: - specifier: ^0.2.1 - version: 0.2.1(next@15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - nice-grpc: - specifier: 2.0.1 - version: 2.0.1 - qrcode.react: - specifier: ^3.1.0 - version: 3.2.0(react@19.1.0) - react: - specifier: 19.1.0 - version: 19.1.0 - react-dom: - specifier: 19.1.0 - version: 19.1.0(react@19.1.0) - react-hook-form: - specifier: 7.39.5 - version: 7.39.5(react@19.1.0) - tinycolor2: - specifier: 1.4.2 - version: 1.4.2 - uuid: - specifier: ^11.1.0 - version: 11.1.0 - devDependencies: - '@babel/eslint-parser': - specifier: ^7.23.0 - version: 7.28.0(@babel/core@7.28.0)(eslint@8.57.1) - '@bufbuild/buf': - specifier: ^1.53.0 - version: 1.56.0 - '@faker-js/faker': - specifier: ^9.7.0 - version: 9.9.0 - '@otplib/core': - specifier: ^12.0.0 - version: 12.0.1 - '@otplib/plugin-crypto': - specifier: ^12.0.0 - version: 12.0.1 - '@otplib/plugin-thirty-two': - specifier: ^12.0.0 - version: 12.0.1 - '@playwright/test': - specifier: ^1.52.0 - version: 1.54.2 - '@testing-library/jest-dom': - specifier: ^6.6.3 - version: 6.6.4 - '@testing-library/react': - specifier: ^16.3.0 - version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@types/ms': - specifier: 2.1.0 - version: 2.1.0 - '@types/node': - specifier: ^22.14.1 - version: 22.17.1 - '@types/react': - specifier: 19.1.2 - version: 19.1.2 - '@types/react-dom': - specifier: 19.1.2 - version: 19.1.2(@types/react@19.1.2) - '@types/tinycolor2': - specifier: 1.4.3 - version: 1.4.3 - '@types/uuid': - specifier: ^10.0.0 - version: 10.0.0 - '@typescript-eslint/eslint-plugin': - specifier: ^7.0.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/parser': - specifier: ^7.0.0 - version: 7.18.0(eslint@8.57.1)(typescript@5.9.2) - '@vercel/git-hooks': - specifier: 1.0.0 - version: 1.0.0 - '@vitejs/plugin-react': - specifier: ^4.4.1 - version: 4.7.0(vite@5.4.19(@types/node@22.17.1)(sass@1.90.0)) - autoprefixer: - specifier: 10.4.21 - version: 10.4.21(postcss@8.5.3) - concurrently: - specifier: ^9.1.2 - version: 9.2.0 - cypress: - specifier: ^14.5.2 - version: 14.5.4 - dotenv-cli: - specifier: ^8.0.0 - version: 8.0.0 - env-cmd: - specifier: ^10.0.0 - version: 10.1.0 - eslint: - specifier: ^8.57.0 - version: 8.57.1 - eslint-config-next: - specifier: 15.4.0-canary.86 - version: 15.4.0-canary.86(eslint@8.57.1)(typescript@5.9.2) - eslint-config-prettier: - specifier: ^9.1.0 - version: 9.1.2(eslint@8.57.1) - gaxios: - specifier: ^7.1.0 - version: 7.1.1 - grpc-tools: - specifier: 1.13.0 - version: 1.13.0 - jsdom: - specifier: ^26.1.0 - version: 26.1.0 - lint-staged: - specifier: 15.5.1 - version: 15.5.1 - make-dir-cli: - specifier: 4.0.0 - version: 4.0.0 - nodemon: - specifier: ^3.1.9 - version: 3.1.10 - postcss: - specifier: 8.5.3 - version: 8.5.3 - prettier: - specifier: ^3.2.5 - version: 3.6.2 - prettier-plugin-organize-imports: - specifier: ^3.2.0 - version: 3.2.4(prettier@3.6.2)(typescript@5.9.2) - prettier-plugin-tailwindcss: - specifier: 0.6.11 - version: 0.6.11(prettier-plugin-organize-imports@3.2.4(prettier@3.6.2)(typescript@5.9.2))(prettier@3.6.2) - sass: - specifier: ^1.87.0 - version: 1.90.0 - start-server-and-test: - specifier: ^2.0.11 - version: 2.0.13 - tailwindcss: - specifier: 3.4.14 - version: 3.4.14 - ts-proto: - specifier: ^2.7.0 - version: 2.7.7 - typescript: - specifier: ^5.8.3 - version: 5.9.2 - vite-tsconfig-paths: - specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.2)(vite@5.4.19(@types/node@22.17.1)(sass@1.90.0)) - vitest: - specifier: ^2.0.0 - version: 2.1.9(@types/node@22.17.1)(jsdom@26.1.0)(sass@1.90.0) - wait-on: - specifier: ^7.2.0 - version: 7.2.0 - -packages: - - '@adobe/css-tools@4.4.4': - resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@asamuzakjp/css-color@3.2.0': - resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} - engines: {node: '>=6.9.0'} - - '@babel/eslint-parser@7.28.0': - resolution: {integrity: sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==} - engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} - peerDependencies: - '@babel/core': ^7.11.0 - eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.2': - resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.28.2': - resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.0': - resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} - engines: {node: '>=6.9.0'} - - '@bufbuild/buf-darwin-arm64@1.56.0': - resolution: {integrity: sha512-9neaI9gx1sxOGl9xrL7kw6H+0WmVAFlIQTIDc3vt1qRhfgOt/8AWOHSOWppGTRjNiB0qh6Xie1LYHv/jgDVN0g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@bufbuild/buf-darwin-x64@1.56.0': - resolution: {integrity: sha512-nRHPMXV8fr/lqU+u/1GGsUg7OvNcxJuCJoJpfRoRg38b+NPzOz2FkQAs5OEJzzprQB5aftn5//cl8YXjgvTuFA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@bufbuild/buf-linux-aarch64@1.56.0': - resolution: {integrity: sha512-+td559RuKNwYDnq49NrIDGJ4F73Ra4QzVVbsC+UeveA0HMnIGRzFbchGjHtNJyaZsI57sXJ7dCHH0iFV3jcYwQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@bufbuild/buf-linux-armv7@1.56.0': - resolution: {integrity: sha512-9v3zmos6wRTBc4QeIg4rfDmPzmTgtUTRCbhr87qws/yddIT8cFtHHhy1whnozBNqtmYOdwZNBNx/QXqGGcRuKw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@bufbuild/buf-linux-x64@1.56.0': - resolution: {integrity: sha512-3jZHHBol1fuichNke7LJtHJUdw314XBj6OuJHY6IufsaaVIj1mtM2DPbGiDhYB453J7FiV/buadctKBxAAHclg==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@bufbuild/buf-win32-arm64@1.56.0': - resolution: {integrity: sha512-KMGzSf9rIbT01Jb2685JovwRRYEdL7Zbs6ZrjyhIHBgKK6cBwz1AJvEaDrWMEzCdv+opQwjgM6UdtA4e9BWP1A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@bufbuild/buf-win32-x64@1.56.0': - resolution: {integrity: sha512-19LFOCyFFVTaaqNGtYTpiF67fcpneWZFlm8UNU+Xs87Kh+N5i/LjDjNytnpFT6snwU4/S+UUkq7WgS6UPjqXIg==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@bufbuild/buf@1.56.0': - resolution: {integrity: sha512-1xQWOf3FCDDTi+5B/VScQ73EP6ACwQPCP4ODvCq2L6IVgFtvYX49ur6cQ2qCM8yFitIHESm/Nbff93sh+V/Iog==} - engines: {node: '>=12'} - hasBin: true - - '@bufbuild/protobuf@2.6.3': - resolution: {integrity: sha512-w/gJKME9mYN7ZoUAmSMAWXk4hkVpxRKvEJCb3dV5g9wwWdxTJJ0ayOJAVcNxtdqaxDyFuC0uz4RSGVacJ030PQ==} - - '@connectrpc/connect-node@2.0.3': - resolution: {integrity: sha512-GZ8WXBCeoZY31wzmnrrV4IA0nvYzEwqt9yHg304b7y/ovKh0IEbBuSWbee/hJu2Tt7PD0C8D4WUwheECCeLpQA==} - engines: {node: '>=18.14.1'} - peerDependencies: - '@bufbuild/protobuf': ^2.2.0 - '@connectrpc/connect': 2.0.3 - - '@connectrpc/connect-web@2.0.3': - resolution: {integrity: sha512-w4LZ2Ci+NW/kcMoHnoczJgyGTmxuv/MQ+tTm2UNL40HimXKWYCAna/fV0AbHRnTiteiwEBpeSCaxF34MetzhAw==} - peerDependencies: - '@bufbuild/protobuf': ^2.2.0 - '@connectrpc/connect': 2.0.3 - - '@connectrpc/connect@2.0.3': - resolution: {integrity: sha512-jAbVMHVtDCydGt2P20VpmLjbLtERqSV0RMSyQF3k2zhK8pzQ2QaCAcyVhufClqrOAFZUKL5BqVYtttaxvhmRgg==} - peerDependencies: - '@bufbuild/protobuf': ^2.2.0 - - '@csstools/color-helpers@5.0.2': - resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} - engines: {node: '>=18'} - - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-color-parser@3.0.10': - resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} - peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 - - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} - - '@cypress/request@3.0.9': - resolution: {integrity: sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==} - engines: {node: '>= 6'} - - '@cypress/xvfb@1.2.4': - resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} - - '@emnapi/core@1.4.5': - resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} - - '@emnapi/runtime@1.4.5': - resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} - - '@emnapi/wasi-threads@1.0.4': - resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} - 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.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@faker-js/faker@9.9.0': - resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} - engines: {node: '>=18.0.0', npm: '>=9.0.0'} - - '@floating-ui/core@1.7.3': - resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - - '@floating-ui/dom@1.7.3': - resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==} - - '@floating-ui/react-dom@2.1.5': - resolution: {integrity: sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/react@0.26.28': - resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - - '@formatjs/ecma402-abstract@2.3.4': - resolution: {integrity: sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==} - - '@formatjs/fast-memoize@2.2.7': - resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} - - '@formatjs/icu-messageformat-parser@2.11.2': - resolution: {integrity: sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==} - - '@formatjs/icu-skeleton-parser@1.8.14': - resolution: {integrity: sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==} - - '@formatjs/intl-localematcher@0.5.10': - resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==} - - '@formatjs/intl-localematcher@0.6.1': - resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} - - '@grpc/grpc-js@1.13.4': - resolution: {integrity: sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==} - engines: {node: '>=12.10.0'} - - '@grpc/proto-loader@0.7.15': - resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} - engines: {node: '>=6'} - hasBin: true - - '@hapi/hoek@9.3.0': - resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} - - '@hapi/topo@5.1.0': - resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - - '@headlessui/react@2.2.7': - resolution: {integrity: sha512-WKdTymY8Y49H8/gUc/lIyYK1M+/6dq0Iywh4zTZVAaiTDprRfioxSgD0wnXTQTBpjpGJuTL1NO/mqEvc//5SSg==} - engines: {node: '>=10'} - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - react-dom: ^18 || ^19 || ^19.0.0-rc - - '@heroicons/react@2.1.3': - resolution: {integrity: sha512-fEcPfo4oN345SoqdlCDdSa4ivjaKbk0jTd+oubcgNxnNgAfzysfwWfQUr+51wigiWHQQRiZNd1Ao0M5Y3M2EGg==} - peerDependencies: - react: '>= 16' - - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - 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.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - - '@img/sharp-darwin-arm64@0.34.3': - resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.3': - resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.0': - resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.0': - resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.0': - resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.2.0': - resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-ppc64@1.2.0': - resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} - cpu: [ppc64] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.2.0': - resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.2.0': - resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.0': - resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.2.0': - resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.34.3': - resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.34.3': - resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-ppc64@0.34.3': - resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - - '@img/sharp-linux-s390x@0.34.3': - resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.34.3': - resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.34.3': - resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.34.3': - resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.34.3': - resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.3': - resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.3': - resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.34.3': - resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.30': - resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} - - '@js-sdsl/ordered-map@4.4.2': - resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} - - '@mapbox/node-pre-gyp@1.0.11': - resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} - hasBin: true - - '@napi-rs/wasm-runtime@0.2.12': - resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - - '@next/env@15.4.0-canary.86': - resolution: {integrity: sha512-WPrEvwqHnjeLx05ncJvqizbBJJFlQGRbxzOnL/pZWKzo19auM9x5Se87P27+E/D/d6jJS801l+thF85lfobAZQ==} - - '@next/eslint-plugin-next@15.4.0-canary.86': - resolution: {integrity: sha512-cOlp6ajA1ptiBxiProcXaNAR88O5ck3IwGJr+A5SnNKU4iTUg4nP0K5lS4Mkage+LAMIQ8dImkLR53PpebXICA==} - - '@next/swc-darwin-arm64@15.4.0-canary.86': - resolution: {integrity: sha512-1ofBmzjPkmoMdM+dXvybZ/Roq8HRo0sFzcwXk7/FJNOufuwyK+QKdSpLE7pHlPR7ZREqfEMj61ONO+gAK+zOJw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@15.4.0-canary.86': - resolution: {integrity: sha512-WCKSrllvwzYi4TgrSdgxKSOF2nhieeaWWOeGucn0OXy50uOAamr0HwP5OaIBCx3oRar4w66gvs4IrdTdMedeJA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@15.4.0-canary.86': - resolution: {integrity: sha512-8qn7DJVNFjhEIDo2ts0YCsO7g+vJjPWh8Ur8lBK3XspeX0BPsF4s+YmgidrpzRXeIfoo2uYLkkXcy/57CVDblw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@next/swc-linux-arm64-musl@15.4.0-canary.86': - resolution: {integrity: sha512-8MTn6N4Ja25neMLu2Bra1lqW9AWPqsYg0BVs5M/cxL0QkcN3mak/8LLX1vbzz7GigMGSA+NLwg+ol8lglfgIGA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@next/swc-linux-x64-gnu@15.4.0-canary.86': - resolution: {integrity: sha512-hIhzDwWDQHnH0M0Pzaqs1c5fa4+LHiLLEBuPJQvhBxQfH+Eh86DWiWHDCaoNiURvdRPg6uCuF2MjwptrMplEkg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@next/swc-linux-x64-musl@15.4.0-canary.86': - resolution: {integrity: sha512-FG6SBuSeRWYMNu6tsfaZ4iDzv3BLxlpRncO2xvKKQPeUdDSQ0cehuHYnx8fRte8IOAJ3rlbRd6NXvrDarqu92Q==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@next/swc-win32-arm64-msvc@15.4.0-canary.86': - resolution: {integrity: sha512-3HvZo4VuyINrNYplRhvC8ILdKwi/vFDHOcTN/I4ru039TFpu2eO6VtXsLBdOdJjGslSSSBYkX+6yRrghihAZDA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@15.4.0-canary.86': - resolution: {integrity: sha512-UO9JzGGj7GhtSJFdI0Bl0dkIIBfgbhXLsgNVmq9Z/CsUsQB6J9RS/BMhsxfVwhO+RETk13nFpNutMAhAwcuD8w==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': - resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} - - '@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'} - - '@nolyfill/is-core-module@1.0.39': - resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} - engines: {node: '>=12.4.0'} - - '@otplib/core@12.0.1': - resolution: {integrity: sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==} - - '@otplib/plugin-crypto@12.0.1': - resolution: {integrity: sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==} - - '@otplib/plugin-thirty-two@12.0.1': - resolution: {integrity: sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==} - - '@parcel/watcher-android-arm64@2.5.1': - resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [android] - - '@parcel/watcher-darwin-arm64@2.5.1': - resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [darwin] - - '@parcel/watcher-darwin-x64@2.5.1': - resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [darwin] - - '@parcel/watcher-freebsd-x64@2.5.1': - resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [freebsd] - - '@parcel/watcher-linux-arm-glibc@2.5.1': - resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - - '@parcel/watcher-linux-arm-musl@2.5.1': - resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - - '@parcel/watcher-linux-arm64-glibc@2.5.1': - resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - - '@parcel/watcher-linux-arm64-musl@2.5.1': - resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - - '@parcel/watcher-linux-x64-glibc@2.5.1': - resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - - '@parcel/watcher-linux-x64-musl@2.5.1': - resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - - '@parcel/watcher-win32-arm64@2.5.1': - resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [win32] - - '@parcel/watcher-win32-ia32@2.5.1': - resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] - - '@parcel/watcher-win32-x64@2.5.1': - resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [win32] - - '@parcel/watcher@2.5.1': - resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} - engines: {node: '>= 10.0.0'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@playwright/test@1.54.2': - resolution: {integrity: sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==} - engines: {node: '>=18'} - hasBin: true - - '@protobufjs/aspromise@1.1.2': - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - - '@protobufjs/base64@1.1.2': - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - - '@protobufjs/codegen@2.0.4': - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} - - '@protobufjs/eventemitter@1.1.0': - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - - '@protobufjs/fetch@1.1.0': - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} - - '@protobufjs/float@1.0.2': - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - - '@protobufjs/inquire@1.1.0': - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - - '@protobufjs/path@1.1.2': - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - - '@protobufjs/pool@1.1.0': - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - - '@protobufjs/utf8@1.1.0': - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - - '@radix-ui/primitive@1.1.2': - resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} - - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-compose-refs@1.1.2': - resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context@1.1.2': - resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.10': - resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-id@1.1.1': - resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-popper@1.2.7': - resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-portal@1.1.9': - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.1.4': - resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.2.3': - resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-tooltip@1.2.7': - resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-use-callback-ref@1.1.1': - resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.2': - resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.2': - resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.1': - resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.1': - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-visually-hidden@1.2.3': - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - - '@react-aria/focus@3.21.0': - resolution: {integrity: sha512-7NEGtTPsBy52EZ/ToVKCu0HSelE3kq9qeis+2eEq90XSuJOMaDHUQrA7RC2Y89tlEwQB31bud/kKRi9Qme1dkA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/interactions@3.25.4': - resolution: {integrity: sha512-HBQMxgUPHrW8V63u9uGgBymkMfj6vdWbB0GgUJY49K9mBKMsypcHeWkWM6+bF7kxRO728/IK8bWDV6whDbqjHg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/ssr@3.9.10': - resolution: {integrity: sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==} - engines: {node: '>= 12'} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-aria/utils@3.30.0': - resolution: {integrity: sha512-ydA6y5G1+gbem3Va2nczj/0G0W7/jUVo/cbN10WA5IizzWIwMP5qhFr7macgbKfHMkZ+YZC3oXnt2NNre5odKw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-stately/flags@3.1.2': - resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==} - - '@react-stately/utils@3.10.8': - resolution: {integrity: sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@react-types/shared@3.31.0': - resolution: {integrity: sha512-ua5U6V66gDcbLZe4P2QeyNgPp4YWD1ymGA6j3n+s8CGExtrCPe64v+g4mvpT8Bnb985R96e4zFT61+m0YCwqMg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@rolldown/pluginutils@1.0.0-beta.27': - resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} - - '@rollup/rollup-android-arm-eabi@4.46.2': - resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.46.2': - resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.46.2': - resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.46.2': - resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.46.2': - resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.46.2': - resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': - resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.46.2': - resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.46.2': - resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.46.2': - resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-ppc64-gnu@4.46.2': - resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.46.2': - resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.46.2': - resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.46.2': - resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.46.2': - resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.46.2': - resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.46.2': - resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.46.2': - resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.46.2': - resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==} - cpu: [x64] - os: [win32] - - '@rtsao/scc@1.1.0': - resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - - '@rushstack/eslint-patch@1.12.0': - resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} - - '@sideway/address@4.1.5': - resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} - - '@sideway/formula@3.0.1': - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} - - '@sideway/pinpoint@2.0.0': - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - - '@swc/helpers@0.5.17': - resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==} - - '@tailwindcss/forms@0.5.7': - resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} - peerDependencies: - tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' - - '@tanstack/react-virtual@3.13.12': - resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/virtual-core@3.13.12': - resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} - - '@testing-library/dom@10.4.1': - resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} - engines: {node: '>=18'} - - '@testing-library/jest-dom@6.6.4': - resolution: {integrity: sha512-xDXgLjVunjHqczScfkCJ9iyjdNOVHvvCdqHSSxwM9L0l/wHkTRum67SDc020uAlCoqktJplgO2AAQeLP1wgqDQ==} - engines: {node: '>=14', npm: '>=6', yarn: '>=1'} - - '@testing-library/react@16.3.0': - resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} - engines: {node: '>=18'} - peerDependencies: - '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 || ^19.0.0 - '@types/react-dom': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@tybys/wasm-util@0.10.0': - resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} - - '@types/aria-query@5.0.4': - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/json5@0.0.29': - resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@22.17.1': - resolution: {integrity: sha512-y3tBaz+rjspDTylNjAX37jEC3TETEFGNJL6uQDxwF9/8GLLIjW1rvVHlynyuUKMnMr1Roq8jOv3vkopBjC4/VA==} - - '@types/react-dom@19.1.2': - resolution: {integrity: sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==} - peerDependencies: - '@types/react': ^19.0.0 - - '@types/react@19.1.2': - resolution: {integrity: sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==} - - '@types/sinonjs__fake-timers@8.1.1': - resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} - - '@types/sizzle@2.3.9': - resolution: {integrity: sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==} - - '@types/tinycolor2@1.4.3': - resolution: {integrity: sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==} - - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - - '@typescript-eslint/eslint-plugin@7.18.0': - resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@7.18.0': - resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@7.18.0': - resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/type-utils@7.18.0': - resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@7.18.0': - resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@typescript-eslint/typescript-estree@7.18.0': - resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@7.18.0': - resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - - '@typescript-eslint/visitor-keys@7.18.0': - resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} - engines: {node: ^18.18.0 || >=20.0.0} - - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - - '@unrs/resolver-binding-android-arm-eabi@1.11.1': - resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} - cpu: [arm] - os: [android] - - '@unrs/resolver-binding-android-arm64@1.11.1': - resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} - cpu: [arm64] - os: [android] - - '@unrs/resolver-binding-darwin-arm64@1.11.1': - resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} - cpu: [arm64] - os: [darwin] - - '@unrs/resolver-binding-darwin-x64@1.11.1': - resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} - cpu: [x64] - os: [darwin] - - '@unrs/resolver-binding-freebsd-x64@1.11.1': - resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} - cpu: [x64] - os: [freebsd] - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': - resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': - resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} - cpu: [arm] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': - resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} - cpu: [arm64] - os: [linux] - - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': - resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} - cpu: [arm64] - os: [linux] - - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': - resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} - cpu: [ppc64] - os: [linux] - - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': - resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} - cpu: [riscv64] - os: [linux] - - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': - resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} - cpu: [riscv64] - os: [linux] - - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': - resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} - cpu: [s390x] - os: [linux] - - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': - resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} - cpu: [x64] - os: [linux] - - '@unrs/resolver-binding-linux-x64-musl@1.11.1': - resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} - cpu: [x64] - os: [linux] - - '@unrs/resolver-binding-wasm32-wasi@1.11.1': - resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': - resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} - cpu: [arm64] - os: [win32] - - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': - resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} - cpu: [ia32] - os: [win32] - - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': - resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} - cpu: [x64] - os: [win32] - - '@vercel/analytics@1.5.0': - resolution: {integrity: sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g==} - peerDependencies: - '@remix-run/react': ^2 - '@sveltejs/kit': ^1 || ^2 - next: '>= 13' - react: ^18 || ^19 || ^19.0.0-rc - svelte: '>= 4' - vue: ^3 - vue-router: ^4 - peerDependenciesMeta: - '@remix-run/react': - optional: true - '@sveltejs/kit': - optional: true - next: - optional: true - react: - optional: true - svelte: - optional: true - vue: - optional: true - vue-router: - optional: true - - '@vercel/git-hooks@1.0.0': - resolution: {integrity: sha512-OxDFAAdyiJ/H0b8zR9rFCu3BIb78LekBXOphOYG3snV4ULhKFX387pBPpqZ9HLiRTejBWBxYEahkw79tuIgdAA==} - - '@vitejs/plugin-react@4.7.0': - resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - - '@vitest/expect@2.1.9': - resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} - - '@vitest/mocker@2.1.9': - resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@2.1.9': - resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} - - '@vitest/runner@2.1.9': - resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} - - '@vitest/snapshot@2.1.9': - resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} - - '@vitest/spy@2.1.9': - resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} - - '@vitest/utils@2.1.9': - resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} - - '@zitadel/client@1.3.1': - resolution: {integrity: sha512-OBbj11mTs57JRfCjMzyaKL+FCSPnPCVc66gmkxkFm/akNx1RpajjZ209E+O9thqTKUlfheuKXOabICRm4XXW+g==} - - '@zitadel/proto@1.3.1': - resolution: {integrity: sha512-ALvvBxNbL1U0O8wCZotIHacDk5Ad+6Hp6hc90kFCw0pxzxvP6v6hiL1zZp1dWz9DxvXRofC75a1cmCS64Kb3yA==} - - abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - - abort-controller-x@0.4.3: - resolution: {integrity: sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA==} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-escapes@7.0.0: - resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} - engines: {node: '>=18'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - aproba@2.1.0: - resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} - - arch@2.2.0: - resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} - - are-we-there-yet@2.0.0: - resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - - 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==} - - aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} - engines: {node: '>= 0.4'} - - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} - - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} - - array.prototype.findlastindex@1.2.6: - resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} - engines: {node: '>= 0.4'} - - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} - - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} - - asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - - assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - 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'} - - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} - - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - aws-sign2@0.7.0: - resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - - aws4@1.13.2: - resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} - - axe-core@4.10.3: - resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} - engines: {node: '>=4'} - - axios@1.11.0: - resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==} - - axobject-query@4.1.0: - resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} - engines: {node: '>= 0.4'} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - blob-util@2.0.2: - resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} - - bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.25.2: - resolution: {integrity: sha512-0si2SJK3ooGzIawRu61ZdPCO1IncZwS8IzuX73sPZsXW6EQ/w/DAfPyKI8l1ETTCr2MnvqWitmlCUxgdul45jA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - cachedir@2.4.0: - resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} - engines: {node: '>=6'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - - caniuse-lite@1.0.30001734: - resolution: {integrity: sha512-uhE1Ye5vgqju6OI71HTQqcBCZrvHugk0MjLak7Q+HfoBgoq5Bi+5YnwjP4fjDgrtYr/l8MVRBvzz9dPD4KyK0A==} - - case-anything@2.1.13: - resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==} - engines: {node: '>=12.13'} - - caseless@0.12.0: - resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - - chai@5.2.1: - resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} - engines: {node: '>=18'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chalk@5.5.0: - resolution: {integrity: sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - check-more-types@2.24.0: - resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} - engines: {node: '>= 0.8.0'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - - ci-info@4.3.0: - resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} - engines: {node: '>=8'} - - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-table3@0.6.1: - resolution: {integrity: sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==} - engines: {node: 10.* || >= 12.*} - - cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} - - cli-truncate@4.0.0: - resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} - engines: {node: '>=18'} - - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - colors@1.4.0: - resolution: {integrity: sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==} - engines: {node: '>=0.1.90'} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@13.1.0: - resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} - engines: {node: '>=18'} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - - common-tags@1.8.2: - resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} - engines: {node: '>=4.0.0'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concurrently@9.2.0: - resolution: {integrity: sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==} - engines: {node: '>=18'} - hasBin: true - - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - copy-to-clipboard@3.3.3: - resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} - - core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - css.escape@1.5.1: - resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssstyle@4.6.0: - resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} - engines: {node: '>=18'} - - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - - cypress@14.5.4: - resolution: {integrity: sha512-0Dhm4qc9VatOcI1GiFGVt8osgpPdqJLHzRwcAB5MSD/CAAts3oybvPUPawHyvJZUd8osADqZe/xzMsZ8sDTjXw==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - damerau-levenshtein@1.0.8: - resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - - dashdash@1.14.1: - resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} - engines: {node: '>=0.10'} - - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - - data-urls@5.0.0: - resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} - engines: {node: '>=18'} - - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - dayjs@1.11.13: - resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - - debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js@10.6.0: - resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - 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'} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} - engines: {node: '>=8'} - - didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - - 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-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - - dom-accessibility-api@0.6.3: - resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} - - dotenv-cli@8.0.0: - resolution: {integrity: sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==} - hasBin: true - - dotenv-expand@10.0.0: - resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} - engines: {node: '>=12'} - - dotenv@16.6.1: - resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} - engines: {node: '>=12'} - - dprint-node@1.0.8: - resolution: {integrity: sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - duplexer@0.1.2: - resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ecc-jsbn@0.1.2: - resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - - electron-to-chromium@1.5.200: - resolution: {integrity: sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w==} - - emoji-regex@10.4.0: - resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - - env-cmd@10.1.0: - resolution: {integrity: sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==} - engines: {node: '>=8.0.0'} - hasBin: true - - environment@1.1.0: - resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} - engines: {node: '>=18'} - - es-abstract@1.24.0: - resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-iterator-helpers@1.2.1: - resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} - - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-config-next@15.4.0-canary.86: - resolution: {integrity: sha512-nMQzamY2GWhvScnfkfOVeq38tCt/TfyJyHMIzVYarpfyRj286Jk8ZkpgzQT8JtyeQ39kxTDZNBrB4CrWODYg4g==} - peerDependencies: - eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 - typescript: '>=3.3.1' - peerDependenciesMeta: - typescript: - optional: true - - eslint-config-prettier@9.1.2: - resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - - eslint-import-resolver-typescript@3.10.1: - resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - eslint: '*' - eslint-plugin-import: '*' - eslint-plugin-import-x: '*' - peerDependenciesMeta: - eslint-plugin-import: - optional: true - eslint-plugin-import-x: - optional: true - - eslint-module-utils@2.12.1: - resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} - 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.32.0: - resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - - eslint-plugin-jsx-a11y@6.10.2: - resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} - engines: {node: '>=4.0'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - - eslint-plugin-react-hooks@5.2.0: - resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - - eslint-plugin-react@7.37.5: - resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - 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.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - event-stream@3.3.4: - resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} - - eventemitter2@6.4.7: - resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} - - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - - execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} - engines: {node: '>=10'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} - - executable@4.1.1: - resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} - engines: {node: '>=4'} - - expect-type@1.2.2: - resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} - engines: {node: '>=12.0.0'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - - extsprintf@1.3.0: - resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} - engines: {'0': node >=0.6.0} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - 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==} - - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - - 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} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - forever-agent@0.6.1: - resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - - form-data@4.0.4: - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} - engines: {node: '>= 6'} - - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - - from@0.1.7: - resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - - fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - 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.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - gauge@3.0.2: - resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - - gaxios@7.1.1: - resolution: {integrity: sha512-Odju3uBUJyVCkW64nLD4wKLhbh93bh6vIg/ZIXkWiLPBrdgtc65+tls/qml+un3pr6JqYVFDZbbmLDQT68rTOQ==} - engines: {node: '>=18'} - - 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-east-asian-width@1.3.0: - resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} - engines: {node: '>=18'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - - getos@3.2.1: - resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} - - getpass@0.1.7: - resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - - 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.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - global-dirs@3.0.1: - resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} - engines: {node: '>=10'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - globrex@0.1.2: - resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - 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==} - - grpc-tools@1.13.0: - resolution: {integrity: sha512-7CbkJ1yWPfX0nHjbYG58BQThNhbICXBZynzCUxCb3LzX5X9B3hQbRY2STiRgIEiLILlK9fgl0z0QVGwPCdXf5g==} - hasBin: true - - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - - hasha@5.2.2: - resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} - engines: {node: '>=8'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - html-encoding-sniffer@4.0.0: - resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} - engines: {node: '>=18'} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - http-signature@1.4.0: - resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==} - engines: {node: '>=0.10'} - - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - - human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore-by-default@1.0.1: - resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - immutable@5.1.3: - resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - 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@2.0.0: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} - - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - intl-messageformat@10.7.16: - resolution: {integrity: sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==} - - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - - is-bun-module@2.0.0: - resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - - is-fullwidth-code-point@5.0.0: - resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} - engines: {node: '>=18'} - - is-generator-function@1.1.0: - resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-installed-globally@0.4.0: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - - 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.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} - - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isstream@0.1.2: - resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - - iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} - hasBin: true - - joi@17.13.3: - resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - - jose@5.10.0: - resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - jsbn@0.1.1: - resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - - jsdom@26.1.0: - resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} - engines: {node: '>=18'} - peerDependencies: - canvas: ^3.0.0 - peerDependenciesMeta: - canvas: - optional: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - - 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 - - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - jsprim@2.0.2: - resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} - engines: {'0': node >=0.6.0} - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - language-subtag-registry@0.3.23: - resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} - - language-tags@1.0.9: - resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} - engines: {node: '>=0.10'} - - lazy-ass@1.6.0: - resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} - engines: {node: '> 0.8'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - lint-staged@15.5.1: - resolution: {integrity: sha512-6m7u8mue4Xn6wK6gZvSCQwBvMBR36xfY24nF5bMTf2MHDYG6S3yhJuOgdYVw99hsjyDt2d4z168b3naI8+NWtQ==} - engines: {node: '>=18.12.0'} - hasBin: true - - listr2@3.14.0: - resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} - engines: {node: '>=10.0.0'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true - - listr2@8.3.3: - resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} - engines: {node: '>=18.0.0'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - log-update@4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} - - log-update@6.1.0: - resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} - engines: {node: '>=18'} - - long@5.3.2: - resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - loupe@3.2.0: - resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lucide-react@0.469.0: - resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - - make-dir-cli@4.0.0: - resolution: {integrity: sha512-9BBC2CaGH0hUAx+tQthgxqYypwkTs+7oXmPdiWyDpHGo4mGB3kdudUKQGivK59C1aJroo4QLlXF7Chu/kdhYiw==} - engines: {node: '>=18'} - hasBin: true - - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - - make-dir@5.0.0: - resolution: {integrity: sha512-G0yBotnlWVonPClw+tq+xi4K7DZC9n96HjGTBDdHkstAVsDkfZhi1sTvZypXLpyQTbISBkDtK0E5XlUqDsShQg==} - engines: {node: '>=18'} - - map-stream@0.1.0: - resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - meow@13.2.0: - resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} - engines: {node: '>=18'} - - 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'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - 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'} - - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - mini-svg-data-uri@1.4.4: - resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} - hasBin: true - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - - moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - napi-postinstall@0.3.3: - resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - hasBin: true - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - - next-intl@3.26.5: - resolution: {integrity: sha512-EQlCIfY0jOhRldiFxwSXG+ImwkQtDEfQeSOEQp6ieAGSLWGlgjdb/Ck/O7wMfC430ZHGeUKVKax8KGusTPKCgg==} - peerDependencies: - next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 - - next-themes@0.2.1: - resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} - peerDependencies: - next: '*' - react: '*' - react-dom: '*' - - next@15.4.0-canary.86: - resolution: {integrity: sha512-lGeO0sOvPZ7oFIklqRA863YzRL1bW+kT/OqU3N6RBquHldiucZwnZKQceZdn6WcHEFmWIHzZV+SMG1JEK7hZLg==} - engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.51.1 - babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - babel-plugin-react-compiler: - optional: true - sass: - optional: true - - nice-grpc-common@2.0.2: - resolution: {integrity: sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==} - - nice-grpc@2.0.1: - resolution: {integrity: sha512-Q5CGXO08STsv+HAkXeFgRayANT62X1LnIDhNXdCf+LP0XaP7EiHM0Cr3QefnoFjDZAx/Kxq+qiQfY66BrtKcNQ==} - - node-addon-api@7.1.1: - resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - - node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - deprecated: Use your platform's native DOMException instead - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - - nodemon@3.1.10: - resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} - engines: {node: '>=10'} - hasBin: true - - nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} - hasBin: true - - 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'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - npmlog@5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. - - nwsapi@2.2.21: - resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.groupby@1.0.3: - resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} - engines: {node: '>= 0.4'} - - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} - - 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'} - - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - ospath@1.2.2: - resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} - - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - - 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.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - - pause-stream@0.0.11: - resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - - performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - pirates@4.0.7: - resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} - engines: {node: '>= 6'} - - playwright-core@1.54.2: - resolution: {integrity: sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.54.2: - resolution: {integrity: sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==} - engines: {node: '>=18'} - hasBin: true - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss-import@15.1.0: - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - - postcss-js@4.0.1: - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss-nested@6.2.0: - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.3: - resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} - engines: {node: ^10 || ^12 || >=14} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-plugin-organize-imports@3.2.4: - resolution: {integrity: sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==} - peerDependencies: - '@volar/vue-language-plugin-pug': ^1.0.4 - '@volar/vue-typescript': ^1.0.4 - prettier: '>=2.0' - typescript: '>=2.9' - peerDependenciesMeta: - '@volar/vue-language-plugin-pug': - optional: true - '@volar/vue-typescript': - optional: true - - prettier-plugin-tailwindcss@0.6.11: - resolution: {integrity: sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==} - engines: {node: '>=14.21.3'} - peerDependencies: - '@ianvs/prettier-plugin-sort-imports': '*' - '@prettier/plugin-pug': '*' - '@shopify/prettier-plugin-liquid': '*' - '@trivago/prettier-plugin-sort-imports': '*' - '@zackad/prettier-plugin-twig': '*' - prettier: ^3.0 - prettier-plugin-astro: '*' - prettier-plugin-css-order: '*' - prettier-plugin-import-sort: '*' - prettier-plugin-jsdoc: '*' - prettier-plugin-marko: '*' - prettier-plugin-multiline-arrays: '*' - prettier-plugin-organize-attributes: '*' - prettier-plugin-organize-imports: '*' - prettier-plugin-sort-imports: '*' - prettier-plugin-style-order: '*' - prettier-plugin-svelte: '*' - peerDependenciesMeta: - '@ianvs/prettier-plugin-sort-imports': - optional: true - '@prettier/plugin-pug': - optional: true - '@shopify/prettier-plugin-liquid': - optional: true - '@trivago/prettier-plugin-sort-imports': - optional: true - '@zackad/prettier-plugin-twig': - optional: true - prettier-plugin-astro: - optional: true - prettier-plugin-css-order: - optional: true - prettier-plugin-import-sort: - optional: true - prettier-plugin-jsdoc: - optional: true - prettier-plugin-marko: - optional: true - prettier-plugin-multiline-arrays: - optional: true - prettier-plugin-organize-attributes: - optional: true - prettier-plugin-organize-imports: - optional: true - prettier-plugin-sort-imports: - optional: true - prettier-plugin-style-order: - optional: true - prettier-plugin-svelte: - optional: true - - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} - engines: {node: '>=14'} - hasBin: true - - pretty-bytes@5.6.0: - resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} - engines: {node: '>=6'} - - pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - protobufjs@7.5.3: - resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==} - engines: {node: '>=12.0.0'} - - proxy-from-env@1.0.0: - resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - ps-tree@1.2.0: - resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} - engines: {node: '>= 0.10'} - hasBin: true - - pstree.remy@1.1.8: - resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} - - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - qrcode.react@3.2.0: - resolution: {integrity: sha512-YietHHltOHA4+l5na1srdaMx4sVSOjV9tamHs+mwiLWAMr6QVACRUw1Neax5CptFILcNoITctJY0Ipyn5enQ8g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - react-dom@19.1.0: - resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} - peerDependencies: - react: ^19.1.0 - - react-hook-form@7.39.5: - resolution: {integrity: sha512-OE0HKyz5IPc6svN2wd+e+evidZrw4O4WZWAWYzQVZuHi+hYnHFSLnxOq0ddjbdmaLIsLHut/ab7j72y2QT3+KA==} - engines: {node: '>=12.22.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} - - react@19.1.0: - resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} - engines: {node: '>=0.10.0'} - - read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - - 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'} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - request-progress@3.0.0: - resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - 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'} - - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rollup@4.46.2: - resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rrweb-cssom@0.8.0: - resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sass@1.90.0: - resolution: {integrity: sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==} - engines: {node: '>=14.0.0'} - hasBin: true - - saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - sharp@0.34.3: - resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - - 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.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} - engines: {node: '>= 0.4'} - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - 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'} - - simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - - simple-update-notifier@2.0.0: - resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} - engines: {node: '>=10'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} - - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - - slice-ansi@7.1.0: - resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} - engines: {node: '>=18'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - split@0.3.3: - resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} - - sshpk@1.18.0: - resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} - engines: {node: '>=0.10.0'} - hasBin: true - - stable-hash@0.0.5: - resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - start-server-and-test@2.0.13: - resolution: {integrity: sha512-G42GCIUjBv/nDoK+QsO+nBdX2Cg3DSAKhSic2DN0GLlK4Q+63TkOeN1cV9PHZKnVOzDKGNVZGCREjpvAIAOdiQ==} - engines: {node: '>=16'} - hasBin: true - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - - stream-combiner@0.0.4: - resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} - - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} - - 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-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string.prototype.includes@2.0.1: - resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} - engines: {node: '>= 0.4'} - - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} - - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-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'} - - styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' - peerDependenciesMeta: - '@babel/core': - optional: true - babel-plugin-macros: - optional: true - - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - - 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-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - - tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - - tailwindcss@3.4.14: - resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} - engines: {node: '>=14.0.0'} - hasBin: true - - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - thirty-two@1.0.2: - resolution: {integrity: sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==} - engines: {node: '>=0.2.6'} - - throttleit@1.0.1: - resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinycolor2@1.4.2: - resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@1.2.0: - resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} - engines: {node: '>=14.0.0'} - - tinyspy@3.0.2: - resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} - engines: {node: '>=14.0.0'} - - tldts-core@6.1.86: - resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} - - tldts@6.1.86: - resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} - hasBin: true - - tmp@0.2.5: - resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} - engines: {node: '>=14.14'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toggle-selection@1.0.6: - resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - - touch@3.1.1: - resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} - hasBin: true - - tough-cookie@5.1.2: - resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} - engines: {node: '>=16'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tr46@5.1.1: - resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} - engines: {node: '>=18'} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - ts-error@1.0.6: - resolution: {integrity: sha512-tLJxacIQUM82IR7JO1UUkKlYuUTmoY9HBJAmNWFzheSlDS5SPMcNIepejHJa4BpPQLAcbRhRf3GDJzyj6rbKvA==} - - ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - - ts-poet@6.12.0: - resolution: {integrity: sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==} - - ts-proto-descriptors@2.0.0: - resolution: {integrity: sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw==} - - ts-proto@2.7.7: - resolution: {integrity: sha512-/OfN9/Yriji2bbpOysZ/Jzc96isOKz+eBTJEcKaIZ0PR6x1TNgVm4Lz0zfbo+J0jwFO7fJjJyssefBPQ0o1V9A==} - hasBin: true - - tsconfck@3.1.6: - resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} - 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.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - 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.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} - - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} - engines: {node: '>=14.17'} - hasBin: true - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - undefsafe@2.0.5: - resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unrs-resolver@1.11.1: - resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - - untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - use-intl@3.26.5: - resolution: {integrity: sha512-OdsJnC/znPvHCHLQH/duvQNXnP1w0hPfS+tkSi3mAbfjYBGh4JnyfdwkQBfIVf7t8gs9eSX/CntxUMvtKdG2MQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 - - use-sync-external-store@1.5.0: - resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} - - vite-node@2.1.9: - resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite-tsconfig-paths@5.1.4: - resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} - peerDependencies: - vite: '*' - peerDependenciesMeta: - vite: - optional: true - - vite@5.4.19: - resolution: {integrity: sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==} - 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: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - vitest@2.1.9: - resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.1.9 - '@vitest/ui': 2.1.9 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - w3c-xmlserializer@5.0.0: - resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} - engines: {node: '>=18'} - - wait-on@7.2.0: - resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} - engines: {node: '>=12.0.0'} - hasBin: true - - wait-on@8.0.4: - resolution: {integrity: sha512-8f9LugAGo4PSc0aLbpKVCVtzayd36sSCp4WLpVngkYq6PK87H79zt77/tlCU6eKCLqR46iFvcl0PU5f+DmtkwA==} - engines: {node: '>=12.0.0'} - hasBin: true - - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - whatwg-encoding@3.1.1: - resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} - engines: {node: '>=18'} - - whatwg-mimetype@4.0.0: - resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} - engines: {node: '>=18'} - - whatwg-url@14.2.0: - resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} - engines: {node: '>=18'} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} - engines: {node: '>= 0.4'} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrap-ansi@9.0.0: - resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} - engines: {node: '>=18'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - 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 - - xml-name-validator@5.0.0: - resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} - engines: {node: '>=18'} - - xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - - 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@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} - engines: {node: '>= 14.6'} - hasBin: true - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - -snapshots: - - '@adobe/css-tools@4.4.4': {} - - '@alloc/quick-lru@5.2.0': {} - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 - - '@asamuzakjp/css-color@3.2.0': - dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - lru-cache: 10.4.3 - - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.28.0': {} - - '@babel/core@7.28.0': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.28.2 - '@babel/parser': 7.28.0 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 - convert-source-map: 2.0.0 - debug: 4.4.1(supports-color@5.5.0) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/eslint-parser@7.28.0(@babel/core@7.28.0)(eslint@8.57.1)': - dependencies: - '@babel/core': 7.28.0 - '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 - eslint: 8.57.1 - eslint-visitor-keys: 2.1.0 - semver: 6.3.1 - - '@babel/generator@7.28.0': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.30 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.27.2': - dependencies: - '@babel/compat-data': 7.28.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.27.1': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.2': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - - '@babel/parser@7.28.0': - dependencies: - '@babel/types': 7.28.2 - - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': - dependencies: - '@babel/core': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/runtime@7.28.2': {} - - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - - '@babel/traverse@7.28.0': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 - '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - debug: 4.4.1(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - '@babel/types@7.28.2': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - - '@bufbuild/buf-darwin-arm64@1.56.0': - optional: true - - '@bufbuild/buf-darwin-x64@1.56.0': - optional: true - - '@bufbuild/buf-linux-aarch64@1.56.0': - optional: true - - '@bufbuild/buf-linux-armv7@1.56.0': - optional: true - - '@bufbuild/buf-linux-x64@1.56.0': - optional: true - - '@bufbuild/buf-win32-arm64@1.56.0': - optional: true - - '@bufbuild/buf-win32-x64@1.56.0': - optional: true - - '@bufbuild/buf@1.56.0': - optionalDependencies: - '@bufbuild/buf-darwin-arm64': 1.56.0 - '@bufbuild/buf-darwin-x64': 1.56.0 - '@bufbuild/buf-linux-aarch64': 1.56.0 - '@bufbuild/buf-linux-armv7': 1.56.0 - '@bufbuild/buf-linux-x64': 1.56.0 - '@bufbuild/buf-win32-arm64': 1.56.0 - '@bufbuild/buf-win32-x64': 1.56.0 - - '@bufbuild/protobuf@2.6.3': {} - - '@connectrpc/connect-node@2.0.3(@bufbuild/protobuf@2.6.3)(@connectrpc/connect@2.0.3(@bufbuild/protobuf@2.6.3))': - dependencies: - '@bufbuild/protobuf': 2.6.3 - '@connectrpc/connect': 2.0.3(@bufbuild/protobuf@2.6.3) - - '@connectrpc/connect-web@2.0.3(@bufbuild/protobuf@2.6.3)(@connectrpc/connect@2.0.3(@bufbuild/protobuf@2.6.3))': - dependencies: - '@bufbuild/protobuf': 2.6.3 - '@connectrpc/connect': 2.0.3(@bufbuild/protobuf@2.6.3) - - '@connectrpc/connect@2.0.3(@bufbuild/protobuf@2.6.3)': - dependencies: - '@bufbuild/protobuf': 2.6.3 - - '@csstools/color-helpers@5.0.2': {} - - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/color-helpers': 5.0.2 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-tokenizer@3.0.4': {} - - '@cypress/request@3.0.9': - dependencies: - aws-sign2: 0.7.0 - aws4: 1.13.2 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 4.0.4 - http-signature: 1.4.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - performance-now: 2.1.0 - qs: 6.14.0 - safe-buffer: 5.2.1 - tough-cookie: 5.1.2 - tunnel-agent: 0.6.0 - uuid: 8.3.2 - - '@cypress/xvfb@1.2.4(supports-color@8.1.1)': - dependencies: - debug: 3.2.7(supports-color@8.1.1) - lodash.once: 4.1.1 - transitivePeerDependencies: - - supports-color - - '@emnapi/core@1.4.5': - dependencies: - '@emnapi/wasi-threads': 1.0.4 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.4.5': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.0.4': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - - '@eslint-community/eslint-utils@4.7.0(eslint@8.57.1)': - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.1': {} - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.12.6 - debug: 4.4.1(supports-color@5.5.0) - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.1': {} - - '@faker-js/faker@9.9.0': {} - - '@floating-ui/core@1.7.3': - dependencies: - '@floating-ui/utils': 0.2.10 - - '@floating-ui/dom@1.7.3': - dependencies: - '@floating-ui/core': 1.7.3 - '@floating-ui/utils': 0.2.10 - - '@floating-ui/react-dom@2.1.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@floating-ui/dom': 1.7.3 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - - '@floating-ui/react@0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@floating-ui/react-dom': 2.1.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@floating-ui/utils': 0.2.10 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - tabbable: 6.2.0 - - '@floating-ui/utils@0.2.10': {} - - '@formatjs/ecma402-abstract@2.3.4': - dependencies: - '@formatjs/fast-memoize': 2.2.7 - '@formatjs/intl-localematcher': 0.6.1 - decimal.js: 10.6.0 - tslib: 2.8.1 - - '@formatjs/fast-memoize@2.2.7': - dependencies: - tslib: 2.8.1 - - '@formatjs/icu-messageformat-parser@2.11.2': - dependencies: - '@formatjs/ecma402-abstract': 2.3.4 - '@formatjs/icu-skeleton-parser': 1.8.14 - tslib: 2.8.1 - - '@formatjs/icu-skeleton-parser@1.8.14': - dependencies: - '@formatjs/ecma402-abstract': 2.3.4 - tslib: 2.8.1 - - '@formatjs/intl-localematcher@0.5.10': - dependencies: - tslib: 2.8.1 - - '@formatjs/intl-localematcher@0.6.1': - dependencies: - tslib: 2.8.1 - - '@grpc/grpc-js@1.13.4': - dependencies: - '@grpc/proto-loader': 0.7.15 - '@js-sdsl/ordered-map': 4.4.2 - - '@grpc/proto-loader@0.7.15': - dependencies: - lodash.camelcase: 4.3.0 - long: 5.3.2 - protobufjs: 7.5.3 - yargs: 17.7.2 - - '@hapi/hoek@9.3.0': {} - - '@hapi/topo@5.1.0': - dependencies: - '@hapi/hoek': 9.3.0 - - '@headlessui/react@2.2.7(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@floating-ui/react': 0.26.28(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/focus': 3.21.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/interactions': 3.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@tanstack/react-virtual': 3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - use-sync-external-store: 1.5.0(react@19.1.0) - - '@heroicons/react@2.1.3(react@19.1.0)': - dependencies: - react: 19.1.0 - - '@humanwhocodes/config-array@0.13.0': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.1(supports-color@5.5.0) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@img/sharp-darwin-arm64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.0 - optional: true - - '@img/sharp-darwin-x64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.0 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.2.0': - optional: true - - '@img/sharp-libvips-darwin-x64@1.2.0': - optional: true - - '@img/sharp-libvips-linux-arm64@1.2.0': - optional: true - - '@img/sharp-libvips-linux-arm@1.2.0': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.0': - optional: true - - '@img/sharp-libvips-linux-s390x@1.2.0': - optional: true - - '@img/sharp-libvips-linux-x64@1.2.0': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.2.0': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.0': - optional: true - - '@img/sharp-linux-arm64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.0 - optional: true - - '@img/sharp-linux-arm@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.0 - optional: true - - '@img/sharp-linux-ppc64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.0 - optional: true - - '@img/sharp-linux-s390x@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.0 - optional: true - - '@img/sharp-linux-x64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.0 - optional: true - - '@img/sharp-linuxmusl-arm64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.3': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.0 - optional: true - - '@img/sharp-wasm32@0.34.3': - dependencies: - '@emnapi/runtime': 1.4.5 - optional: true - - '@img/sharp-win32-arm64@0.34.3': - optional: true - - '@img/sharp-win32-ia32@0.34.3': - optional: true - - '@img/sharp-win32-x64@0.34.3': - optional: true - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.30 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.30': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@js-sdsl/ordered-map@4.4.2': {} - - '@mapbox/node-pre-gyp@1.0.11': - dependencies: - detect-libc: 2.0.4 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.7.0 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.7.2 - tar: 6.2.1 - transitivePeerDependencies: - - encoding - - supports-color - - '@napi-rs/wasm-runtime@0.2.12': - dependencies: - '@emnapi/core': 1.4.5 - '@emnapi/runtime': 1.4.5 - '@tybys/wasm-util': 0.10.0 - optional: true - - '@next/env@15.4.0-canary.86': {} - - '@next/eslint-plugin-next@15.4.0-canary.86': - dependencies: - fast-glob: 3.3.1 - - '@next/swc-darwin-arm64@15.4.0-canary.86': - optional: true - - '@next/swc-darwin-x64@15.4.0-canary.86': - optional: true - - '@next/swc-linux-arm64-gnu@15.4.0-canary.86': - optional: true - - '@next/swc-linux-arm64-musl@15.4.0-canary.86': - optional: true - - '@next/swc-linux-x64-gnu@15.4.0-canary.86': - optional: true - - '@next/swc-linux-x64-musl@15.4.0-canary.86': - optional: true - - '@next/swc-win32-arm64-msvc@15.4.0-canary.86': - optional: true - - '@next/swc-win32-x64-msvc@15.4.0-canary.86': - optional: true - - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': - dependencies: - eslint-scope: 5.1.1 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 - - '@nolyfill/is-core-module@1.0.39': {} - - '@otplib/core@12.0.1': {} - - '@otplib/plugin-crypto@12.0.1': - dependencies: - '@otplib/core': 12.0.1 - - '@otplib/plugin-thirty-two@12.0.1': - dependencies: - '@otplib/core': 12.0.1 - thirty-two: 1.0.2 - - '@parcel/watcher-android-arm64@2.5.1': - optional: true - - '@parcel/watcher-darwin-arm64@2.5.1': - optional: true - - '@parcel/watcher-darwin-x64@2.5.1': - optional: true - - '@parcel/watcher-freebsd-x64@2.5.1': - optional: true - - '@parcel/watcher-linux-arm-glibc@2.5.1': - optional: true - - '@parcel/watcher-linux-arm-musl@2.5.1': - optional: true - - '@parcel/watcher-linux-arm64-glibc@2.5.1': - optional: true - - '@parcel/watcher-linux-arm64-musl@2.5.1': - optional: true - - '@parcel/watcher-linux-x64-glibc@2.5.1': - optional: true - - '@parcel/watcher-linux-x64-musl@2.5.1': - optional: true - - '@parcel/watcher-win32-arm64@2.5.1': - optional: true - - '@parcel/watcher-win32-ia32@2.5.1': - optional: true - - '@parcel/watcher-win32-x64@2.5.1': - optional: true - - '@parcel/watcher@2.5.1': - dependencies: - detect-libc: 1.0.3 - is-glob: 4.0.3 - micromatch: 4.0.8 - node-addon-api: 7.1.1 - optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.1 - '@parcel/watcher-darwin-arm64': 2.5.1 - '@parcel/watcher-darwin-x64': 2.5.1 - '@parcel/watcher-freebsd-x64': 2.5.1 - '@parcel/watcher-linux-arm-glibc': 2.5.1 - '@parcel/watcher-linux-arm-musl': 2.5.1 - '@parcel/watcher-linux-arm64-glibc': 2.5.1 - '@parcel/watcher-linux-arm64-musl': 2.5.1 - '@parcel/watcher-linux-x64-glibc': 2.5.1 - '@parcel/watcher-linux-x64-musl': 2.5.1 - '@parcel/watcher-win32-arm64': 2.5.1 - '@parcel/watcher-win32-ia32': 2.5.1 - '@parcel/watcher-win32-x64': 2.5.1 - optional: true - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@playwright/test@1.54.2': - dependencies: - playwright: 1.54.2 - - '@protobufjs/aspromise@1.1.2': {} - - '@protobufjs/base64@1.1.2': {} - - '@protobufjs/codegen@2.0.4': {} - - '@protobufjs/eventemitter@1.1.0': {} - - '@protobufjs/fetch@1.1.0': - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 - - '@protobufjs/float@1.0.2': {} - - '@protobufjs/inquire@1.1.0': {} - - '@protobufjs/path@1.1.2': {} - - '@protobufjs/pool@1.1.0': {} - - '@protobufjs/utf8@1.1.0': {} - - '@radix-ui/primitive@1.1.2': {} - - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.2)(react@19.1.0)': - dependencies: - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-context@1.1.2(@types/react@19.1.2)(react@19.1.0)': - dependencies: - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@radix-ui/react-id@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@floating-ui/react-dom': 2.1.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/rect': 1.1.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@radix-ui/react-slot@1.2.3(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/primitive': 1.1.2 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.2)(react@19.1.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/rect': 1.1.1 - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-use-size@1.1.1(@types/react@19.1.2)(react@19.1.0)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0) - react: 19.1.0 - optionalDependencies: - '@types/react': 19.1.2 - - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@radix-ui/rect@1.1.1': {} - - '@react-aria/focus@3.21.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@react-aria/interactions': 3.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-aria/utils': 3.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-types/shared': 3.31.0(react@19.1.0) - '@swc/helpers': 0.5.17 - clsx: 2.1.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - - '@react-aria/interactions@3.25.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@react-aria/ssr': 3.9.10(react@19.1.0) - '@react-aria/utils': 3.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - '@react-stately/flags': 3.1.2 - '@react-types/shared': 3.31.0(react@19.1.0) - '@swc/helpers': 0.5.17 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - - '@react-aria/ssr@3.9.10(react@19.1.0)': - dependencies: - '@swc/helpers': 0.5.17 - react: 19.1.0 - - '@react-aria/utils@3.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@react-aria/ssr': 3.9.10(react@19.1.0) - '@react-stately/flags': 3.1.2 - '@react-stately/utils': 3.10.8(react@19.1.0) - '@react-types/shared': 3.31.0(react@19.1.0) - '@swc/helpers': 0.5.17 - clsx: 2.1.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - - '@react-stately/flags@3.1.2': - dependencies: - '@swc/helpers': 0.5.17 - - '@react-stately/utils@3.10.8(react@19.1.0)': - dependencies: - '@swc/helpers': 0.5.17 - react: 19.1.0 - - '@react-types/shared@3.31.0(react@19.1.0)': - dependencies: - react: 19.1.0 - - '@rolldown/pluginutils@1.0.0-beta.27': {} - - '@rollup/rollup-android-arm-eabi@4.46.2': - optional: true - - '@rollup/rollup-android-arm64@4.46.2': - optional: true - - '@rollup/rollup-darwin-arm64@4.46.2': - optional: true - - '@rollup/rollup-darwin-x64@4.46.2': - optional: true - - '@rollup/rollup-freebsd-arm64@4.46.2': - optional: true - - '@rollup/rollup-freebsd-x64@4.46.2': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.46.2': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.46.2': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.46.2': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.46.2': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.46.2': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.46.2': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.46.2': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.46.2': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.46.2': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.46.2': - optional: true - - '@rollup/rollup-linux-x64-musl@4.46.2': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.46.2': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.46.2': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.46.2': - optional: true - - '@rtsao/scc@1.1.0': {} - - '@rushstack/eslint-patch@1.12.0': {} - - '@sideway/address@4.1.5': - dependencies: - '@hapi/hoek': 9.3.0 - - '@sideway/formula@3.0.1': {} - - '@sideway/pinpoint@2.0.0': {} - - '@swc/helpers@0.5.15': - dependencies: - tslib: 2.8.1 - - '@swc/helpers@0.5.17': - dependencies: - tslib: 2.8.1 - - '@tailwindcss/forms@0.5.7(tailwindcss@3.4.14)': - dependencies: - mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.14 - - '@tanstack/react-virtual@3.13.12(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@tanstack/virtual-core': 3.13.12 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - - '@tanstack/virtual-core@3.13.12': {} - - '@testing-library/dom@10.4.1': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.28.2 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - picocolors: 1.1.1 - pretty-format: 27.5.1 - - '@testing-library/jest-dom@6.6.4': - dependencies: - '@adobe/css-tools': 4.4.4 - aria-query: 5.3.2 - css.escape: 1.5.1 - dom-accessibility-api: 0.6.3 - lodash: 4.17.21 - picocolors: 1.1.1 - redent: 3.0.0 - - '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': - dependencies: - '@babel/runtime': 7.28.2 - '@testing-library/dom': 10.4.1 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - optionalDependencies: - '@types/react': 19.1.2 - '@types/react-dom': 19.1.2(@types/react@19.1.2) - - '@tybys/wasm-util@0.10.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/aria-query@5.0.4': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 - - '@types/babel__generator@7.27.0': - dependencies: - '@babel/types': 7.28.2 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.28.2 - - '@types/estree@1.0.8': {} - - '@types/json5@0.0.29': {} - - '@types/ms@2.1.0': {} - - '@types/node@22.17.1': - dependencies: - undici-types: 6.21.0 - - '@types/react-dom@19.1.2(@types/react@19.1.2)': - dependencies: - '@types/react': 19.1.2 - - '@types/react@19.1.2': - dependencies: - csstype: 3.1.3 - - '@types/sinonjs__fake-timers@8.1.1': {} - - '@types/sizzle@2.3.9': {} - - '@types/tinycolor2@1.4.3': {} - - '@types/uuid@10.0.0': {} - - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 22.17.1 - optional: true - - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - ts-api-utils: 1.4.3(typescript@5.9.2) - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2)': - dependencies: - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.1(supports-color@5.5.0) - eslint: 8.57.1 - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@7.18.0': - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - - '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)': - dependencies: - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) - '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - debug: 4.4.1(supports-color@5.5.0) - eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.9.2) - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@7.18.0': {} - - '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.2)': - dependencies: - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/visitor-keys': 7.18.0 - debug: 4.4.1(supports-color@5.5.0) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@5.9.2) - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)': - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) - '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/types': 7.18.0 - '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) - eslint: 8.57.1 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@7.18.0': - dependencies: - '@typescript-eslint/types': 7.18.0 - eslint-visitor-keys: 3.4.3 - - '@ungap/structured-clone@1.3.0': {} - - '@unrs/resolver-binding-android-arm-eabi@1.11.1': - optional: true - - '@unrs/resolver-binding-android-arm64@1.11.1': - optional: true - - '@unrs/resolver-binding-darwin-arm64@1.11.1': - optional: true - - '@unrs/resolver-binding-darwin-x64@1.11.1': - optional: true - - '@unrs/resolver-binding-freebsd-x64@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-arm64-musl@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-x64-gnu@1.11.1': - optional: true - - '@unrs/resolver-binding-linux-x64-musl@1.11.1': - optional: true - - '@unrs/resolver-binding-wasm32-wasi@1.11.1': - dependencies: - '@napi-rs/wasm-runtime': 0.2.12 - optional: true - - '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': - optional: true - - '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': - optional: true - - '@unrs/resolver-binding-win32-x64-msvc@1.11.1': - optional: true - - '@vercel/analytics@1.5.0(next@15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0))(react@19.1.0)': - optionalDependencies: - next: 15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0) - react: 19.1.0 - - '@vercel/git-hooks@1.0.0': {} - - '@vitejs/plugin-react@4.7.0(vite@5.4.19(@types/node@22.17.1)(sass@1.90.0))': - dependencies: - '@babel/core': 7.28.0 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) - '@rolldown/pluginutils': 1.0.0-beta.27 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 5.4.19(@types/node@22.17.1)(sass@1.90.0) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@2.1.9': - dependencies: - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.2.1 - tinyrainbow: 1.2.0 - - '@vitest/mocker@2.1.9(vite@5.4.19(@types/node@22.17.1)(sass@1.90.0))': - dependencies: - '@vitest/spy': 2.1.9 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 5.4.19(@types/node@22.17.1)(sass@1.90.0) - - '@vitest/pretty-format@2.1.9': - dependencies: - tinyrainbow: 1.2.0 - - '@vitest/runner@2.1.9': - dependencies: - '@vitest/utils': 2.1.9 - pathe: 1.1.2 - - '@vitest/snapshot@2.1.9': - dependencies: - '@vitest/pretty-format': 2.1.9 - magic-string: 0.30.17 - pathe: 1.1.2 - - '@vitest/spy@2.1.9': - dependencies: - tinyspy: 3.0.2 - - '@vitest/utils@2.1.9': - dependencies: - '@vitest/pretty-format': 2.1.9 - loupe: 3.2.0 - tinyrainbow: 1.2.0 - - '@zitadel/client@1.3.1': - dependencies: - '@bufbuild/protobuf': 2.6.3 - '@connectrpc/connect': 2.0.3(@bufbuild/protobuf@2.6.3) - '@connectrpc/connect-node': 2.0.3(@bufbuild/protobuf@2.6.3)(@connectrpc/connect@2.0.3(@bufbuild/protobuf@2.6.3)) - '@connectrpc/connect-web': 2.0.3(@bufbuild/protobuf@2.6.3)(@connectrpc/connect@2.0.3(@bufbuild/protobuf@2.6.3)) - '@zitadel/proto': 1.3.1 - jose: 5.10.0 - - '@zitadel/proto@1.3.1': - dependencies: - '@bufbuild/protobuf': 2.6.3 - - abbrev@1.1.1: {} - - abort-controller-x@0.4.3: {} - - acorn-jsx@5.3.2(acorn@8.15.0): - dependencies: - acorn: 8.15.0 - - acorn@8.15.0: {} - - agent-base@6.0.2: - dependencies: - debug: 4.4.1(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - agent-base@7.1.4: {} - - aggregate-error@3.1.0: - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - - 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 - - ansi-colors@4.1.3: {} - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - - ansi-escapes@7.0.0: - dependencies: - environment: 1.1.0 - - ansi-regex@5.0.1: {} - - ansi-regex@6.1.0: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@5.2.0: {} - - ansi-styles@6.2.1: {} - - any-promise@1.3.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - aproba@2.1.0: {} - - arch@2.2.0: {} - - are-we-there-yet@2.0.0: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - - arg@5.0.2: {} - - argparse@2.0.1: {} - - aria-query@5.3.0: - dependencies: - dequal: 2.0.3 - - aria-query@5.3.2: {} - - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 - - array-includes@3.1.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - is-string: 1.1.1 - math-intrinsics: 1.1.0 - - array-union@2.1.0: {} - - array.prototype.findlast@1.2.5: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - array.prototype.findlastindex@1.2.6: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - array.prototype.flat@1.3.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-shim-unscopables: 1.1.0 - - array.prototype.flatmap@1.3.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-shim-unscopables: 1.1.0 - - array.prototype.tosorted@1.1.4: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-errors: 1.3.0 - es-shim-unscopables: 1.1.0 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 - - asn1@0.2.6: - dependencies: - safer-buffer: 2.1.2 - - assert-plus@1.0.0: {} - - assertion-error@2.0.1: {} - - ast-types-flow@0.0.8: {} - - astral-regex@2.0.0: {} - - async-function@1.0.0: {} - - async@3.2.6: {} - - asynckit@0.4.0: {} - - at-least-node@1.0.0: {} - - autoprefixer@10.4.21(postcss@8.5.3): - dependencies: - browserslist: 4.25.2 - caniuse-lite: 1.0.30001734 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.1 - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - aws-sign2@0.7.0: {} - - aws4@1.13.2: {} - - axe-core@4.10.3: {} - - axios@1.11.0(debug@4.4.1): - dependencies: - follow-redirects: 1.15.11(debug@4.4.1) - form-data: 4.0.4 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - axobject-query@4.1.0: {} - - balanced-match@1.0.2: {} - - base64-js@1.5.1: {} - - bcrypt-pbkdf@1.0.2: - dependencies: - tweetnacl: 0.14.5 - - binary-extensions@2.3.0: {} - - blob-util@2.0.2: {} - - bluebird@3.7.2: {} - - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.25.2: - dependencies: - caniuse-lite: 1.0.30001734 - electron-to-chromium: 1.5.200 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.2) - - buffer-crc32@0.2.13: {} - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - cac@6.7.14: {} - - cachedir@2.4.0: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.8: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - callsites@3.1.0: {} - - camelcase-css@2.0.1: {} - - caniuse-lite@1.0.30001734: {} - - case-anything@2.1.13: {} - - caseless@0.12.0: {} - - chai@5.2.1: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.2.0 - pathval: 2.0.1 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@5.5.0: {} - - check-error@2.1.1: {} - - check-more-types@2.24.0: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - chownr@2.0.0: {} - - ci-info@4.3.0: {} - - clean-stack@2.2.0: {} - - cli-cursor@3.1.0: - dependencies: - restore-cursor: 3.1.0 - - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-table3@0.6.1: - dependencies: - string-width: 4.2.3 - optionalDependencies: - colors: 1.4.0 - - cli-truncate@2.1.0: - dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 - - cli-truncate@4.0.0: - dependencies: - slice-ansi: 5.0.0 - string-width: 7.2.0 - - client-only@0.0.1: {} - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - clsx@1.2.1: {} - - clsx@2.1.1: {} - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 - optional: true - - color-support@1.1.3: {} - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - optional: true - - colorette@2.0.20: {} - - colors@1.4.0: - optional: true - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - commander@13.1.0: {} - - commander@4.1.1: {} - - commander@6.2.1: {} - - common-tags@1.8.2: {} - - concat-map@0.0.1: {} - - concurrently@9.2.0: - dependencies: - chalk: 4.1.2 - lodash: 4.17.21 - rxjs: 7.8.2 - shell-quote: 1.8.3 - supports-color: 8.1.1 - tree-kill: 1.2.2 - yargs: 17.7.2 - - console-control-strings@1.1.0: {} - - convert-source-map@2.0.0: {} - - copy-to-clipboard@3.3.3: - dependencies: - toggle-selection: 1.0.6 - - core-util-is@1.0.2: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - css.escape@1.5.1: {} - - cssesc@3.0.0: {} - - cssstyle@4.6.0: - dependencies: - '@asamuzakjp/css-color': 3.2.0 - rrweb-cssom: 0.8.0 - - csstype@3.1.3: {} - - cypress@14.5.4: - dependencies: - '@cypress/request': 3.0.9 - '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.9 - arch: 2.2.0 - blob-util: 2.0.2 - bluebird: 3.7.2 - buffer: 5.7.1 - cachedir: 2.4.0 - chalk: 4.1.2 - check-more-types: 2.24.0 - ci-info: 4.3.0 - cli-cursor: 3.1.0 - cli-table3: 0.6.1 - commander: 6.2.1 - common-tags: 1.8.2 - dayjs: 1.11.13 - debug: 4.4.1(supports-color@8.1.1) - enquirer: 2.4.1 - eventemitter2: 6.4.7 - execa: 4.1.0 - executable: 4.1.1 - extract-zip: 2.0.1(supports-color@8.1.1) - figures: 3.2.0 - fs-extra: 9.1.0 - getos: 3.2.1 - hasha: 5.2.2 - is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0(enquirer@2.4.1) - lodash: 4.17.21 - log-symbols: 4.1.0 - minimist: 1.2.8 - ospath: 1.2.2 - pretty-bytes: 5.6.0 - process: 0.11.10 - proxy-from-env: 1.0.0 - request-progress: 3.0.0 - semver: 7.7.2 - supports-color: 8.1.1 - tmp: 0.2.5 - tree-kill: 1.2.2 - untildify: 4.0.0 - yauzl: 2.10.0 - - damerau-levenshtein@1.0.8: {} - - dashdash@1.14.1: - dependencies: - assert-plus: 1.0.0 - - data-uri-to-buffer@4.0.1: {} - - data-urls@5.0.0: - dependencies: - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-offset@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - dayjs@1.11.13: {} - - debug@3.2.7(supports-color@8.1.1): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 - - debug@4.4.1(supports-color@5.5.0): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 5.5.0 - - debug@4.4.1(supports-color@8.1.1): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 - - decimal.js@10.6.0: {} - - deep-eql@5.0.2: {} - - deep-is@0.1.4: {} - - deepmerge@4.3.1: {} - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - delayed-stream@1.0.0: {} - - delegates@1.0.0: {} - - dequal@2.0.3: {} - - detect-libc@1.0.3: {} - - detect-libc@2.0.4: {} - - didyoumean@1.2.2: {} - - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - - dlv@1.1.3: {} - - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 - - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - - dom-accessibility-api@0.5.16: {} - - dom-accessibility-api@0.6.3: {} - - dotenv-cli@8.0.0: - dependencies: - cross-spawn: 7.0.6 - dotenv: 16.6.1 - dotenv-expand: 10.0.0 - minimist: 1.2.8 - - dotenv-expand@10.0.0: {} - - dotenv@16.6.1: {} - - dprint-node@1.0.8: - dependencies: - detect-libc: 1.0.3 - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - duplexer@0.1.2: {} - - eastasianwidth@0.2.0: {} - - ecc-jsbn@0.1.2: - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - - electron-to-chromium@1.5.200: {} - - emoji-regex@10.4.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - - entities@6.0.1: {} - - env-cmd@10.1.0: - dependencies: - commander: 4.1.1 - cross-spawn: 7.0.6 - - environment@1.1.0: {} - - es-abstract@1.24.0: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.19 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-iterator-helpers@1.2.1: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-errors: 1.3.0 - es-set-tostringtag: 2.1.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - iterator.prototype: 1.1.5 - safe-array-concat: 1.1.3 - - es-module-lexer@1.7.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - es-shim-unscopables@1.1.0: - dependencies: - hasown: 2.0.2 - - es-to-primitive@1.3.0: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - - escalade@3.2.0: {} - - escape-string-regexp@1.0.5: {} - - escape-string-regexp@4.0.0: {} - - eslint-config-next@15.4.0-canary.86(eslint@8.57.1)(typescript@5.9.2): - dependencies: - '@next/eslint-plugin-next': 15.4.0-canary.86 - '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) - eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) - eslint-plugin-react: 7.37.5(eslint@8.57.1) - eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - eslint-import-resolver-webpack - - eslint-plugin-import-x - - supports-color - - eslint-config-prettier@9.1.2(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - - eslint-import-resolver-node@0.3.9: - dependencies: - debug: 3.2.7(supports-color@8.1.1) - is-core-module: 2.16.1 - resolve: 1.22.10 - transitivePeerDependencies: - - supports-color - - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): - dependencies: - '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1(supports-color@5.5.0) - eslint: 8.57.1 - get-tsconfig: 4.10.1 - is-bun-module: 2.0.0 - stable-hash: 0.0.5 - tinyglobby: 0.2.14 - unrs-resolver: 1.11.1 - optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): - dependencies: - debug: 3.2.7(supports-color@8.1.1) - optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - - eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): - dependencies: - '@rtsao/scc': 1.1.0 - array-includes: 3.1.9 - array.prototype.findlastindex: 1.2.6 - array.prototype.flat: 1.3.3 - array.prototype.flatmap: 1.3.3 - debug: 3.2.7(supports-color@8.1.1) - doctrine: 2.1.0 - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) - hasown: 2.0.2 - is-core-module: 2.16.1 - is-glob: 4.0.3 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - object.groupby: 1.0.3 - object.values: 1.2.1 - semver: 6.3.1 - string.prototype.trimend: 1.0.9 - tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) - transitivePeerDependencies: - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - - eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): - dependencies: - aria-query: 5.3.2 - array-includes: 3.1.9 - array.prototype.flatmap: 1.3.3 - ast-types-flow: 0.0.8 - axe-core: 4.10.3 - axobject-query: 4.1.0 - damerau-levenshtein: 1.0.8 - emoji-regex: 9.2.2 - eslint: 8.57.1 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - language-tags: 1.0.9 - minimatch: 3.1.2 - object.fromentries: 2.0.8 - safe-regex-test: 1.1.0 - string.prototype.includes: 2.0.1 - - eslint-plugin-react-hooks@5.2.0(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - - eslint-plugin-react@7.37.5(eslint@8.57.1): - dependencies: - array-includes: 3.1.9 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.3 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.2.1 - eslint: 8.57.1 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.9 - object.fromentries: 2.0.8 - object.values: 1.2.1 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.12 - string.prototype.repeat: 1.0.0 - - eslint-scope@5.1.1: - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@2.1.0: {} - - eslint-visitor-keys@3.4.3: {} - - eslint@8.57.1: - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.1(supports-color@5.5.0) - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - espree@9.6.1: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 3.4.3 - - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@4.3.0: {} - - estraverse@5.3.0: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - esutils@2.0.3: {} - - event-stream@3.3.4: - dependencies: - duplexer: 0.1.2 - from: 0.1.7 - map-stream: 0.1.0 - pause-stream: 0.0.11 - split: 0.3.3 - stream-combiner: 0.0.4 - through: 2.3.8 - - eventemitter2@6.4.7: {} - - eventemitter3@5.0.1: {} - - execa@4.1.0: - dependencies: - cross-spawn: 7.0.6 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - execa@8.0.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 - - executable@4.1.1: - dependencies: - pify: 2.3.0 - - expect-type@1.2.2: {} - - extend@3.0.2: {} - - extract-zip@2.0.1(supports-color@8.1.1): - dependencies: - debug: 4.4.1(supports-color@8.1.1) - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - - extsprintf@1.3.0: {} - - fast-deep-equal@3.1.3: {} - - fast-glob@3.3.1: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fastq@1.19.1: - dependencies: - reusify: 1.1.0 - - fd-slicer@1.1.0: - dependencies: - pend: 1.2.0 - - fdir@6.4.6(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - - figures@3.2.0: - dependencies: - escape-string-regexp: 1.0.5 - - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@3.2.0: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - rimraf: 3.0.2 - - flatted@3.3.3: {} - - follow-redirects@1.15.11(debug@4.4.1): - optionalDependencies: - debug: 4.4.1(supports-color@5.5.0) - - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - forever-agent@0.6.1: {} - - form-data@4.0.4: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.2 - mime-types: 2.1.35 - - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 - - fraction.js@4.3.7: {} - - from@0.1.7: {} - - fs-extra@9.1.0: - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - - fs.realpath@1.0.0: {} - - fsevents@2.3.2: - optional: true - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - function.prototype.name@1.1.8: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.2 - is-callable: 1.2.7 - - functions-have-names@1.2.3: {} - - gauge@3.0.2: - dependencies: - aproba: 2.1.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - - gaxios@7.1.1: - dependencies: - extend: 3.0.2 - https-proxy-agent: 7.0.6 - node-fetch: 3.3.2 - transitivePeerDependencies: - - supports-color - - gensync@1.0.0-beta.2: {} - - get-caller-file@2.0.5: {} - - get-east-asian-width@1.3.0: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-stream@5.2.0: - dependencies: - pump: 3.0.3 - - get-stream@6.0.1: {} - - get-stream@8.0.1: {} - - get-symbol-description@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - - get-tsconfig@4.10.1: - dependencies: - resolve-pkg-maps: 1.0.0 - - getos@3.2.1: - dependencies: - async: 3.2.6 - - getpass@0.1.7: - dependencies: - assert-plus: 1.0.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - global-dirs@3.0.1: - dependencies: - ini: 2.0.0 - - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - - globrex@0.1.2: {} - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - - grpc-tools@1.13.0: - dependencies: - '@mapbox/node-pre-gyp': 1.0.11 - transitivePeerDependencies: - - encoding - - supports-color - - has-bigints@1.1.0: {} - - has-flag@3.0.0: {} - - has-flag@4.0.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - has-unicode@2.0.1: {} - - hasha@5.2.2: - dependencies: - is-stream: 2.0.1 - type-fest: 0.8.1 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - html-encoding-sniffer@4.0.0: - dependencies: - whatwg-encoding: 3.1.1 - - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - http-signature@1.4.0: - dependencies: - assert-plus: 1.0.0 - jsprim: 2.0.2 - sshpk: 1.18.0 - - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.1(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - human-signals@1.1.1: {} - - human-signals@2.1.0: {} - - human-signals@5.0.0: {} - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - ieee754@1.2.1: {} - - ignore-by-default@1.0.1: {} - - ignore@5.3.2: {} - - immutable@5.1.3: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - indent-string@4.0.0: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - ini@2.0.0: {} - - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 - - intl-messageformat@10.7.16: - dependencies: - '@formatjs/ecma402-abstract': 2.3.4 - '@formatjs/fast-memoize': 2.2.7 - '@formatjs/icu-messageformat-parser': 2.11.2 - tslib: 2.8.1 - - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-arrayish@0.3.2: - optional: true - - is-async-function@2.1.1: - dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-boolean-object@1.2.2: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-bun-module@2.0.0: - dependencies: - semver: 7.7.2 - - is-callable@1.2.7: {} - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 - - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-extglob@2.1.1: {} - - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-fullwidth-code-point@3.0.0: {} - - is-fullwidth-code-point@4.0.0: {} - - is-fullwidth-code-point@5.0.0: - dependencies: - get-east-asian-width: 1.3.0 - - is-generator-function@1.1.0: - dependencies: - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-installed-globally@0.4.0: - dependencies: - global-dirs: 3.0.1 - is-path-inside: 3.0.3 - - is-map@2.0.3: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-number@7.0.0: {} - - is-path-inside@3.0.3: {} - - is-potential-custom-element-name@1.0.1: {} - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 - - is-stream@2.0.1: {} - - is-stream@3.0.0: {} - - is-string@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.19 - - is-typedarray@1.0.0: {} - - is-unicode-supported@0.1.0: {} - - is-weakmap@2.0.2: {} - - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - isarray@2.0.5: {} - - isexe@2.0.0: {} - - isstream@0.1.2: {} - - iterator.prototype@1.1.5: - dependencies: - define-data-property: 1.1.4 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - has-symbols: 1.1.0 - set-function-name: 2.0.2 - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - jiti@1.21.7: {} - - joi@17.13.3: - dependencies: - '@hapi/hoek': 9.3.0 - '@hapi/topo': 5.1.0 - '@sideway/address': 4.1.5 - '@sideway/formula': 3.0.1 - '@sideway/pinpoint': 2.0.0 - - jose@5.10.0: {} - - js-tokens@4.0.0: {} - - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - - jsbn@0.1.1: {} - - jsdom@26.1.0: - dependencies: - cssstyle: 4.6.0 - data-urls: 5.0.0 - decimal.js: 10.6.0 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.21 - parse5: 7.3.0 - rrweb-cssom: 0.8.0 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 5.1.2 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.2.0 - ws: 8.18.3 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - jsesc@3.1.0: {} - - json-buffer@3.0.1: {} - - json-schema-traverse@0.4.1: {} - - json-schema@0.4.0: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - json-stringify-safe@5.0.1: {} - - json5@1.0.2: - dependencies: - minimist: 1.2.8 - - json5@2.2.3: {} - - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - jsprim@2.0.2: - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - - jsx-ast-utils@3.3.5: - dependencies: - array-includes: 3.1.9 - array.prototype.flat: 1.3.3 - object.assign: 4.1.7 - object.values: 1.2.1 - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - language-subtag-registry@0.3.23: {} - - language-tags@1.0.9: - dependencies: - language-subtag-registry: 0.3.23 - - lazy-ass@1.6.0: {} - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lilconfig@2.1.0: {} - - lilconfig@3.1.3: {} - - lines-and-columns@1.2.4: {} - - lint-staged@15.5.1: - dependencies: - chalk: 5.5.0 - commander: 13.1.0 - debug: 4.4.1(supports-color@5.5.0) - execa: 8.0.1 - lilconfig: 3.1.3 - listr2: 8.3.3 - micromatch: 4.0.8 - pidtree: 0.6.0 - string-argv: 0.3.2 - yaml: 2.8.1 - transitivePeerDependencies: - - supports-color - - listr2@3.14.0(enquirer@2.4.1): - dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.20 - log-update: 4.0.0 - p-map: 4.0.0 - rfdc: 1.4.1 - rxjs: 7.8.2 - through: 2.3.8 - wrap-ansi: 7.0.0 - optionalDependencies: - enquirer: 2.4.1 - - listr2@8.3.3: - dependencies: - cli-truncate: 4.0.0 - colorette: 2.0.20 - eventemitter3: 5.0.1 - log-update: 6.1.0 - rfdc: 1.4.1 - wrap-ansi: 9.0.0 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.camelcase@4.3.0: {} - - lodash.merge@4.6.2: {} - - lodash.once@4.1.1: {} - - lodash@4.17.21: {} - - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - - log-update@4.0.0: - dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 - - log-update@6.1.0: - dependencies: - ansi-escapes: 7.0.0 - cli-cursor: 5.0.0 - slice-ansi: 7.1.0 - strip-ansi: 7.1.0 - wrap-ansi: 9.0.0 - - long@5.3.2: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - loupe@3.2.0: {} - - lru-cache@10.4.3: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lucide-react@0.469.0(react@19.1.0): - dependencies: - react: 19.1.0 - - lz-string@1.5.0: {} - - magic-string@0.30.17: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - make-dir-cli@4.0.0: - dependencies: - make-dir: 5.0.0 - meow: 13.2.0 - - make-dir@3.1.0: - dependencies: - semver: 6.3.1 - - make-dir@5.0.0: {} - - map-stream@0.1.0: {} - - math-intrinsics@1.1.0: {} - - meow@13.2.0: {} - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mimic-fn@2.1.0: {} - - mimic-fn@4.0.0: {} - - mimic-function@5.0.1: {} - - min-indent@1.0.1: {} - - mini-svg-data-uri@1.4.4: {} - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - - minimist@1.2.8: {} - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - - minipass@7.1.2: {} - - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - - mkdirp@1.0.4: {} - - moment@2.30.1: {} - - ms@2.1.3: {} - - mz@2.7.0: - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - - nanoid@3.3.11: {} - - napi-postinstall@0.3.3: {} - - natural-compare@1.4.0: {} - - negotiator@1.0.0: {} - - next-intl@3.26.5(next@15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0))(react@19.1.0): - dependencies: - '@formatjs/intl-localematcher': 0.5.10 - negotiator: 1.0.0 - next: 15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0) - react: 19.1.0 - use-intl: 3.26.5(react@19.1.0) - - next-themes@0.2.1(next@15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): - dependencies: - next: 15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0) - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - - next@15.4.0-canary.86(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.90.0): - dependencies: - '@next/env': 15.4.0-canary.86 - '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001734 - postcss: 8.4.31 - react: 19.1.0 - react-dom: 19.1.0(react@19.1.0) - styled-jsx: 5.1.6(@babel/core@7.28.0)(react@19.1.0) - optionalDependencies: - '@next/swc-darwin-arm64': 15.4.0-canary.86 - '@next/swc-darwin-x64': 15.4.0-canary.86 - '@next/swc-linux-arm64-gnu': 15.4.0-canary.86 - '@next/swc-linux-arm64-musl': 15.4.0-canary.86 - '@next/swc-linux-x64-gnu': 15.4.0-canary.86 - '@next/swc-linux-x64-musl': 15.4.0-canary.86 - '@next/swc-win32-arm64-msvc': 15.4.0-canary.86 - '@next/swc-win32-x64-msvc': 15.4.0-canary.86 - '@playwright/test': 1.54.2 - sass: 1.90.0 - sharp: 0.34.3 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - - nice-grpc-common@2.0.2: - dependencies: - ts-error: 1.0.6 - - nice-grpc@2.0.1: - dependencies: - '@grpc/grpc-js': 1.13.4 - abort-controller-x: 0.4.3 - nice-grpc-common: 2.0.2 - - node-addon-api@7.1.1: - optional: true - - node-domexception@1.0.0: {} - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - - node-releases@2.0.19: {} - - nodemon@3.1.10: - dependencies: - chokidar: 3.6.0 - debug: 4.4.1(supports-color@5.5.0) - ignore-by-default: 1.0.1 - minimatch: 3.1.2 - pstree.remy: 1.1.8 - semver: 7.7.2 - simple-update-notifier: 2.0.0 - supports-color: 5.5.0 - touch: 3.1.1 - undefsafe: 2.0.5 - - nopt@5.0.0: - dependencies: - abbrev: 1.1.1 - - normalize-path@3.0.0: {} - - normalize-range@0.1.2: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - - npmlog@5.0.1: - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 - - nwsapi@2.2.21: {} - - object-assign@4.1.1: {} - - object-hash@3.0.0: {} - - object-inspect@1.13.4: {} - - object-keys@1.1.1: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - object.entries@1.1.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-object-atoms: 1.1.1 - - object.groupby@1.0.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - - object.values@1.2.1: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - ospath@1.2.2: {} - - own-keys@1.0.1: - dependencies: - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-map@4.0.0: - dependencies: - aggregate-error: 3.1.0 - - package-json-from-dist@1.0.1: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse5@7.3.0: - dependencies: - entities: 6.0.1 - - path-exists@4.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-key@4.0.0: {} - - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - - path-type@4.0.0: {} - - pathe@1.1.2: {} - - pathval@2.0.1: {} - - pause-stream@0.0.11: - dependencies: - through: 2.3.8 - - pend@1.2.0: {} - - performance-now@2.1.0: {} - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.3: {} - - pidtree@0.6.0: {} - - pify@2.3.0: {} - - pirates@4.0.7: {} - - playwright-core@1.54.2: {} - - playwright@1.54.2: - dependencies: - playwright-core: 1.54.2 - optionalDependencies: - fsevents: 2.3.2 - - possible-typed-array-names@1.1.0: {} - - postcss-import@15.1.0(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.10 - - postcss-js@4.0.1(postcss@8.5.3): - dependencies: - camelcase-css: 2.0.1 - postcss: 8.5.3 - - postcss-load-config@4.0.2(postcss@8.5.3): - dependencies: - lilconfig: 3.1.3 - yaml: 2.8.1 - optionalDependencies: - postcss: 8.5.3 - - postcss-nested@6.2.0(postcss@8.5.3): - dependencies: - postcss: 8.5.3 - postcss-selector-parser: 6.1.2 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-value-parser@4.2.0: {} - - postcss@8.4.31: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.3: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prelude-ls@1.2.1: {} - - prettier-plugin-organize-imports@3.2.4(prettier@3.6.2)(typescript@5.9.2): - dependencies: - prettier: 3.6.2 - typescript: 5.9.2 - - prettier-plugin-tailwindcss@0.6.11(prettier-plugin-organize-imports@3.2.4(prettier@3.6.2)(typescript@5.9.2))(prettier@3.6.2): - dependencies: - prettier: 3.6.2 - optionalDependencies: - prettier-plugin-organize-imports: 3.2.4(prettier@3.6.2)(typescript@5.9.2) - - prettier@3.6.2: {} - - pretty-bytes@5.6.0: {} - - pretty-format@27.5.1: - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - - process@0.11.10: {} - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - protobufjs@7.5.3: - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 22.17.1 - long: 5.3.2 - - proxy-from-env@1.0.0: {} - - proxy-from-env@1.1.0: {} - - ps-tree@1.2.0: - dependencies: - event-stream: 3.3.4 - - pstree.remy@1.1.8: {} - - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - punycode@2.3.1: {} - - qrcode.react@3.2.0(react@19.1.0): - dependencies: - react: 19.1.0 - - qs@6.14.0: - dependencies: - side-channel: 1.1.0 - - queue-microtask@1.2.3: {} - - react-dom@19.1.0(react@19.1.0): - dependencies: - react: 19.1.0 - scheduler: 0.26.0 - - react-hook-form@7.39.5(react@19.1.0): - dependencies: - react: 19.1.0 - - react-is@16.13.1: {} - - react-is@17.0.2: {} - - react-refresh@0.17.0: {} - - react@19.1.0: {} - - read-cache@1.0.0: - dependencies: - pify: 2.3.0 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - readdirp@4.1.2: {} - - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 - - regexp.prototype.flags@1.5.4: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 - - request-progress@3.0.0: - dependencies: - throttleit: 1.0.1 - - require-directory@2.1.1: {} - - resolve-from@4.0.0: {} - - resolve-pkg-maps@1.0.0: {} - - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - resolve@2.0.0-next.5: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - restore-cursor@3.1.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - - reusify@1.1.0: {} - - rfdc@1.4.1: {} - - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - - rollup@4.46.2: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.46.2 - '@rollup/rollup-android-arm64': 4.46.2 - '@rollup/rollup-darwin-arm64': 4.46.2 - '@rollup/rollup-darwin-x64': 4.46.2 - '@rollup/rollup-freebsd-arm64': 4.46.2 - '@rollup/rollup-freebsd-x64': 4.46.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.46.2 - '@rollup/rollup-linux-arm-musleabihf': 4.46.2 - '@rollup/rollup-linux-arm64-gnu': 4.46.2 - '@rollup/rollup-linux-arm64-musl': 4.46.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.46.2 - '@rollup/rollup-linux-ppc64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-gnu': 4.46.2 - '@rollup/rollup-linux-riscv64-musl': 4.46.2 - '@rollup/rollup-linux-s390x-gnu': 4.46.2 - '@rollup/rollup-linux-x64-gnu': 4.46.2 - '@rollup/rollup-linux-x64-musl': 4.46.2 - '@rollup/rollup-win32-arm64-msvc': 4.46.2 - '@rollup/rollup-win32-ia32-msvc': 4.46.2 - '@rollup/rollup-win32-x64-msvc': 4.46.2 - fsevents: 2.3.3 - - rrweb-cssom@0.8.0: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - - safe-array-concat@1.1.3: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 - - safe-buffer@5.2.1: {} - - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 - - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - safer-buffer@2.1.2: {} - - sass@1.90.0: - dependencies: - chokidar: 4.0.3 - immutable: 5.1.3 - source-map-js: 1.2.1 - optionalDependencies: - '@parcel/watcher': 2.5.1 - - saxes@6.0.0: - dependencies: - xmlchars: 2.2.0 - - scheduler@0.26.0: {} - - semver@6.3.1: {} - - semver@7.7.2: {} - - set-blocking@2.0.0: {} - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - - set-proto@1.0.0: - dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - - sharp@0.34.3: - dependencies: - color: 4.2.3 - detect-libc: 2.0.4 - semver: 7.7.2 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.3 - '@img/sharp-darwin-x64': 0.34.3 - '@img/sharp-libvips-darwin-arm64': 1.2.0 - '@img/sharp-libvips-darwin-x64': 1.2.0 - '@img/sharp-libvips-linux-arm': 1.2.0 - '@img/sharp-libvips-linux-arm64': 1.2.0 - '@img/sharp-libvips-linux-ppc64': 1.2.0 - '@img/sharp-libvips-linux-s390x': 1.2.0 - '@img/sharp-libvips-linux-x64': 1.2.0 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 - '@img/sharp-libvips-linuxmusl-x64': 1.2.0 - '@img/sharp-linux-arm': 0.34.3 - '@img/sharp-linux-arm64': 0.34.3 - '@img/sharp-linux-ppc64': 0.34.3 - '@img/sharp-linux-s390x': 0.34.3 - '@img/sharp-linux-x64': 0.34.3 - '@img/sharp-linuxmusl-arm64': 0.34.3 - '@img/sharp-linuxmusl-x64': 0.34.3 - '@img/sharp-wasm32': 0.34.3 - '@img/sharp-win32-arm64': 0.34.3 - '@img/sharp-win32-ia32': 0.34.3 - '@img/sharp-win32-x64': 0.34.3 - optional: true - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - shell-quote@1.8.3: {} - - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - siginfo@2.0.0: {} - - signal-exit@3.0.7: {} - - signal-exit@4.1.0: {} - - simple-swizzle@0.2.2: - dependencies: - is-arrayish: 0.3.2 - optional: true - - simple-update-notifier@2.0.0: - dependencies: - semver: 7.7.2 - - slash@3.0.0: {} - - slice-ansi@3.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - - slice-ansi@4.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - - slice-ansi@5.0.0: - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 4.0.0 - - slice-ansi@7.1.0: - dependencies: - ansi-styles: 6.2.1 - is-fullwidth-code-point: 5.0.0 - - source-map-js@1.2.1: {} - - split@0.3.3: - dependencies: - through: 2.3.8 - - sshpk@1.18.0: - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 - - stable-hash@0.0.5: {} - - stackback@0.0.2: {} - - start-server-and-test@2.0.13: - dependencies: - arg: 5.0.2 - bluebird: 3.7.2 - check-more-types: 2.24.0 - debug: 4.4.1(supports-color@5.5.0) - execa: 5.1.1 - lazy-ass: 1.6.0 - ps-tree: 1.2.0 - wait-on: 8.0.4(debug@4.4.1) - transitivePeerDependencies: - - supports-color - - std-env@3.9.0: {} - - stop-iteration-iterator@1.1.0: - dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 - - stream-combiner@0.0.4: - dependencies: - duplexer: 0.1.2 - - string-argv@0.3.2: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - string-width@7.2.0: - dependencies: - emoji-regex: 10.4.0 - get-east-asian-width: 1.3.0 - strip-ansi: 7.1.0 - - string.prototype.includes@2.0.1: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.0 - - string.prototype.matchall@4.0.12: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - regexp.prototype.flags: 1.5.4 - set-function-name: 2.0.2 - side-channel: 1.1.0 - - string.prototype.repeat@1.0.0: - dependencies: - define-properties: 1.2.1 - es-abstract: 1.24.0 - - string.prototype.trim@1.2.10: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.0 - es-object-atoms: 1.1.1 - has-property-descriptors: 1.0.2 - - string.prototype.trimend@1.0.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.1.0 - - strip-bom@3.0.0: {} - - strip-final-newline@2.0.0: {} - - strip-final-newline@3.0.0: {} - - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - - strip-json-comments@3.1.1: {} - - styled-jsx@5.1.6(@babel/core@7.28.0)(react@19.1.0): - dependencies: - client-only: 0.0.1 - react: 19.1.0 - optionalDependencies: - '@babel/core': 7.28.0 - - sucrase@3.35.0: - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - commander: 4.1.1 - glob: 10.4.5 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.7 - ts-interface-checker: 0.1.13 - - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - symbol-tree@3.2.4: {} - - tabbable@6.2.0: {} - - tailwindcss@3.4.14: - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.3 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.7 - lilconfig: 2.1.0 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.1.1 - postcss: 8.5.3 - postcss-import: 15.1.0(postcss@8.5.3) - postcss-js: 4.0.1(postcss@8.5.3) - postcss-load-config: 4.0.2(postcss@8.5.3) - postcss-nested: 6.2.0(postcss@8.5.3) - postcss-selector-parser: 6.1.2 - resolve: 1.22.10 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node - - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - - text-table@0.2.0: {} - - thenify-all@1.6.0: - dependencies: - thenify: 3.3.1 - - thenify@3.3.1: - dependencies: - any-promise: 1.3.0 - - thirty-two@1.0.2: {} - - throttleit@1.0.1: {} - - through@2.3.8: {} - - tinybench@2.9.0: {} - - tinycolor2@1.4.2: {} - - tinyexec@0.3.2: {} - - tinyglobby@0.2.14: - dependencies: - fdir: 6.4.6(picomatch@4.0.3) - picomatch: 4.0.3 - - tinypool@1.1.1: {} - - tinyrainbow@1.2.0: {} - - tinyspy@3.0.2: {} - - tldts-core@6.1.86: {} - - tldts@6.1.86: - dependencies: - tldts-core: 6.1.86 - - tmp@0.2.5: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - toggle-selection@1.0.6: {} - - touch@3.1.1: {} - - tough-cookie@5.1.2: - dependencies: - tldts: 6.1.86 - - tr46@0.0.3: {} - - tr46@5.1.1: - dependencies: - punycode: 2.3.1 - - tree-kill@1.2.2: {} - - ts-api-utils@1.4.3(typescript@5.9.2): - dependencies: - typescript: 5.9.2 - - ts-error@1.0.6: {} - - ts-interface-checker@0.1.13: {} - - ts-poet@6.12.0: - dependencies: - dprint-node: 1.0.8 - - ts-proto-descriptors@2.0.0: - dependencies: - '@bufbuild/protobuf': 2.6.3 - - ts-proto@2.7.7: - dependencies: - '@bufbuild/protobuf': 2.6.3 - case-anything: 2.1.13 - ts-poet: 6.12.0 - ts-proto-descriptors: 2.0.0 - - tsconfck@3.1.6(typescript@5.9.2): - optionalDependencies: - typescript: 5.9.2 - - tsconfig-paths@3.15.0: - dependencies: - '@types/json5': 0.0.29 - json5: 1.0.2 - minimist: 1.2.8 - strip-bom: 3.0.0 - - tslib@2.8.1: {} - - tunnel-agent@0.6.0: - dependencies: - safe-buffer: 5.2.1 - - tweetnacl@0.14.5: {} - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - type-fest@0.20.2: {} - - type-fest@0.21.3: {} - - type-fest@0.8.1: {} - - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typed-array-byte-length@1.0.3: - dependencies: - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - - typed-array-byte-offset@1.0.4: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 - - typed-array-length@1.0.7: - dependencies: - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 - - typescript@5.9.2: {} - - unbox-primitive@1.1.0: - dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 - - undefsafe@2.0.5: {} - - undici-types@6.21.0: {} - - universalify@2.0.1: {} - - unrs-resolver@1.11.1: - dependencies: - napi-postinstall: 0.3.3 - optionalDependencies: - '@unrs/resolver-binding-android-arm-eabi': 1.11.1 - '@unrs/resolver-binding-android-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-arm64': 1.11.1 - '@unrs/resolver-binding-darwin-x64': 1.11.1 - '@unrs/resolver-binding-freebsd-x64': 1.11.1 - '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 - '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 - '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 - '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 - '@unrs/resolver-binding-linux-x64-musl': 1.11.1 - '@unrs/resolver-binding-wasm32-wasi': 1.11.1 - '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 - '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 - '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - - untildify@4.0.0: {} - - update-browserslist-db@1.1.3(browserslist@4.25.2): - dependencies: - browserslist: 4.25.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - use-intl@3.26.5(react@19.1.0): - dependencies: - '@formatjs/fast-memoize': 2.2.7 - intl-messageformat: 10.7.16 - react: 19.1.0 - - use-sync-external-store@1.5.0(react@19.1.0): - dependencies: - react: 19.1.0 - - util-deprecate@1.0.2: {} - - uuid@11.1.0: {} - - uuid@8.3.2: {} - - verror@1.10.0: - dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 - - vite-node@2.1.9(@types/node@22.17.1)(sass@1.90.0): - dependencies: - cac: 6.7.14 - debug: 4.4.1(supports-color@5.5.0) - es-module-lexer: 1.7.0 - pathe: 1.1.2 - vite: 5.4.19(@types/node@22.17.1)(sass@1.90.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@5.4.19(@types/node@22.17.1)(sass@1.90.0)): - dependencies: - debug: 4.4.1(supports-color@5.5.0) - globrex: 0.1.2 - tsconfck: 3.1.6(typescript@5.9.2) - optionalDependencies: - vite: 5.4.19(@types/node@22.17.1)(sass@1.90.0) - transitivePeerDependencies: - - supports-color - - typescript - - vite@5.4.19(@types/node@22.17.1)(sass@1.90.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.3 - rollup: 4.46.2 - optionalDependencies: - '@types/node': 22.17.1 - fsevents: 2.3.3 - sass: 1.90.0 - - vitest@2.1.9(@types/node@22.17.1)(jsdom@26.1.0)(sass@1.90.0): - dependencies: - '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.19(@types/node@22.17.1)(sass@1.90.0)) - '@vitest/pretty-format': 2.1.9 - '@vitest/runner': 2.1.9 - '@vitest/snapshot': 2.1.9 - '@vitest/spy': 2.1.9 - '@vitest/utils': 2.1.9 - chai: 5.2.1 - debug: 4.4.1(supports-color@5.5.0) - expect-type: 1.2.2 - magic-string: 0.30.17 - pathe: 1.1.2 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinypool: 1.1.1 - tinyrainbow: 1.2.0 - vite: 5.4.19(@types/node@22.17.1)(sass@1.90.0) - vite-node: 2.1.9(@types/node@22.17.1)(sass@1.90.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.17.1 - jsdom: 26.1.0 - transitivePeerDependencies: - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - w3c-xmlserializer@5.0.0: - dependencies: - xml-name-validator: 5.0.0 - - wait-on@7.2.0: - dependencies: - axios: 1.11.0(debug@4.4.1) - joi: 17.13.3 - lodash: 4.17.21 - minimist: 1.2.8 - rxjs: 7.8.2 - transitivePeerDependencies: - - debug - - wait-on@8.0.4(debug@4.4.1): - dependencies: - axios: 1.11.0(debug@4.4.1) - joi: 17.13.3 - lodash: 4.17.21 - minimist: 1.2.8 - rxjs: 7.8.2 - transitivePeerDependencies: - - debug - - web-streams-polyfill@3.3.3: {} - - webidl-conversions@3.0.1: {} - - webidl-conversions@7.0.0: {} - - whatwg-encoding@3.1.1: - dependencies: - iconv-lite: 0.6.3 - - whatwg-mimetype@4.0.0: {} - - whatwg-url@14.2.0: - dependencies: - tr46: 5.1.1 - webidl-conversions: 7.0.0 - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - which-boxed-primitive@1.1.1: - dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 - - which-builtin-type@1.2.1: - dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.1.8 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.0 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.19 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 - - which-typed-array@1.1.19: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - - word-wrap@1.2.5: {} - - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - wrap-ansi@9.0.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 7.2.0 - strip-ansi: 7.1.0 - - wrappy@1.0.2: {} - - ws@8.18.3: {} - - xml-name-validator@5.0.0: {} - - xmlchars@2.2.0: {} - - y18n@5.0.8: {} - - yallist@3.1.1: {} - - yallist@4.0.0: {} - - yaml@2.8.1: {} - - yargs-parser@21.1.1: {} - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yauzl@2.10.0: - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - - yocto-queue@0.1.0: {} diff --git a/project.json b/project.json new file mode 100644 index 0000000000..223fbd52b9 --- /dev/null +++ b/project.json @@ -0,0 +1,156 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "targets": { + "prod": { + "description": "Runs the Next.js Login application in production mode from the standalone build", + "continuous": true, + "dependsOn": [ + "build" + ], + "defaultConfiguration": "default", + "configurations": { + "default": {}, + "test-login-integration": {} + } + }, + "dev": { + "description": "Runs the Next.js Login application in development mode with hot-reloading", + "continuous": true, + "dependsOn": [ + "^build" + ] + }, + "build": { + "description": "Builds the Next.js Login application in standalone mode for production", + "cache": true, + "dependsOn": [ + "^build" + ], + "inputs": [ + "default", + "{workspaceRoot}/pnpm-lock.yaml", + "!{projectRoot}/.env.*", + "!{projectRoot}/.local.env", + "!{projectRoot}/integration/**/*", + "!{projectRoot}/acceptance/**/*", + "!{projectRoot}/cypress.config.ts" + ], + "outputs": [ + "{projectRoot}/.next/standalone" + ] + }, + "build-vercel": { + "description": "Builds the Next.js Login application for Vercel deployment", + "cache": true, + "dependsOn": [ + "^build" + ], + "inputs": [ + "default", + "{workspaceRoot}/pnpm-lock.yaml", + "!{projectRoot}/.env.*", + "!{projectRoot}/.local.env", + "!{projectRoot}/integration/**/*", + "!{projectRoot}/acceptance/**/*", + "!{projectRoot}/cypress.config.ts" + ], + "outputs": [ + "{projectRoot}/.next", + "!{projectRoot}/.next/cache", + "!{projectRoot}/.next/standalone" + ] + }, + "lint": { + "description": "Runs all linters", + "dependsOn": [ + "lint-check-*" + ] + }, + "lint-check-prettier": { + "description": "Checks code formatting with Prettier", + "cache": true, + "inputs": [ + "default" + ] + }, + "lint-check-next": { + "description": "Runs Next.js specific lint checks", + "cache": true, + "inputs": [ + "default" + ] + }, + "test": { + "description": "Runs all tests (unit and integration)", + "dependsOn": [ + "test-unit", + "test-integration" + ] + }, + "test-unit": { + "description": "Runs unit tests using Vitest", + "dependsOn": [ + "^build" + ] + }, + "test-integration-run-login": { + "description": "Runs the Login application under test. It has its own target, separate from test-integration, because it's a continuous task.", + "dependsOn": [ + "build" + ], + "continuous": true, + "command": "nx run @zitadel/login:prod:test-login-integration --excludeTaskDependencies" + }, + "test-integration": { + "description": "Runs integration tests using Cypress against a running Login and a mocked API", + "dependsOn": [ + "test-integration-run-login", + "@zitadel/login-api-mock:build", + "@zitadel/login-api-mock:serve" + ], + "executor": "nx:run-commands", + "options": { + "cwd": "{projectRoot}", + "parallel": false, + "commands": [ + "pnpm cypress install", + "pnpm wait-on --verbose --interval 2000 --simultaneous 1 --timeout 30m \"tcp:${API_MOCK_STUBS_HOST}:22220\" \"http-get://localhost:3001/ui/v2/login/verify?userId=221394658884845598&code=abc\"", + "DISPLAY='' pnpm cypress run --headless", + "nx run @zitadel/login:test-integration-stop" + ] + }, + "inputs": [ + "default", + "{workspaceRoot}/pnpm-lock.yaml", + "!{projectRoot}/acceptance/**/*", + { "env": "LOGIN_BASE_URL" } + ] + }, + "test-integration-stop": { + "description": "Stops the gRPC mock container used for integration tests.", + "command": "nx run @zitadel/login-api-mock:down" + }, + "pack": { + "description": "Packages the standalone Login application build into an archive", + "dependsOn": [ + "build" + ], + "executor": "nx:run-commands", + "options": { + "parallel": false, + "env": { + "STANDALONE_DIR": "{projectRoot}/.next/standalone", + "PACK_DIR": "{workspaceRoot}/.artifacts/pack" + }, + "commands": [ + "mkdir -p ${PACK_DIR}", + "tar -czvf ${PACK_DIR}/zitadel-login-standalone.tar.gz -C ${STANDALONE_DIR} ." + ] + }, + "cache": true, + "outputs": [ + "{workspaceRoot}/.artifacts/pack/zitadel-login-*.tar.gz" + ] + } + } +} \ No newline at end of file diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 5bafc0012d..f16d5ed506 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -1,11 +1,18 @@ #!/bin/sh -set -o allexport -. /.env-file/.env -set +o allexport -if [ -n "${ZITADEL_SERVICE_USER_TOKEN_FILE}" ] && [ -f "${ZITADEL_SERVICE_USER_TOKEN_FILE}" ]; then - echo "ZITADEL_SERVICE_USER_TOKEN_FILE=${ZITADEL_SERVICE_USER_TOKEN_FILE} is set and file exists, setting ZITADEL_SERVICE_USER_TOKEN to the files content" - export ZITADEL_SERVICE_USER_TOKEN=$(cat "${ZITADEL_SERVICE_USER_TOKEN_FILE}") +if [ -f /.env-file/.env ]; then + set -o allexport + . /.env-file/.env + set +o allexport fi -exec node /runtime/apps/login/server.js +if [ -n "${ZITADEL_SERVICE_USER_TOKEN_FILE}" ]; then + echo "ZITADEL_SERVICE_USER_TOKEN_FILE=${ZITADEL_SERVICE_USER_TOKEN_FILE} is set. Awaiting file and reading token." + while [ ! -f "${ZITADEL_SERVICE_USER_TOKEN_FILE}" ]; do + sleep 2 + done + echo "token file found, reading token" + export ZITADEL_SERVICE_USER_TOKEN=$(cat "${ZITADEL_SERVICE_USER_TOKEN_FILE}") +fi + +exec $@ diff --git a/src/app/(login)/accounts/page.tsx b/src/app/(login)/accounts/page.tsx index e4e6b387dc..50407e9964 100644 --- a/src/app/(login)/accounts/page.tsx +++ b/src/app/(login)/accounts/page.tsx @@ -10,10 +10,17 @@ import { } from "@/lib/zitadel"; import { UserPlusIcon } from "@heroicons/react/24/outline"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; // import { getLocale } from "next-intl/server"; import { headers } from "next/headers"; import Link from "next/link"; +export async function generateMetadata(): Promise { + const t = await getTranslations("accounts"); + return { title: t('title')}; +} + async function loadSessions({ serviceUrl }: { serviceUrl: string }) { const cookieIds = await getAllSessionCookieIds(); diff --git a/src/app/(login)/authenticator/set/page.tsx b/src/app/(login)/authenticator/set/page.tsx index e08367f589..1f8912d95c 100644 --- a/src/app/(login)/authenticator/set/page.tsx +++ b/src/app/(login)/authenticator/set/page.tsx @@ -19,9 +19,16 @@ import { } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; // import { getLocale } from "next-intl/server"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; +export async function generateMetadata(): Promise { + const t = await getTranslations("authenticator"); + return { title: t('title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { diff --git a/src/app/(login)/device/page.tsx b/src/app/(login)/device/page.tsx index e8761d25de..0164640a49 100644 --- a/src/app/(login)/device/page.tsx +++ b/src/app/(login)/device/page.tsx @@ -4,8 +4,15 @@ import { Translated } from "@/components/translated"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getBrandingSettings, getDefaultOrg } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("device"); + return { title: t('usercode.title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { diff --git a/src/app/(login)/idp/[provider]/success/page.tsx b/src/app/(login)/idp/[provider]/success/page.tsx index ae9feff6b7..1acf2a39c6 100644 --- a/src/app/(login)/idp/[provider]/success/page.tsx +++ b/src/app/(login)/idp/[provider]/success/page.tsx @@ -51,8 +51,7 @@ async function resolveOrganizationForUser({ serviceUrl, domain: suffix, }); - const orgToCheckForDiscovery = - orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; + const orgToCheckForDiscovery = orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; if (orgToCheckForDiscovery) { const orgLoginSettings = await getLoginSettings({ @@ -141,12 +140,7 @@ export default async function Page(props: { } } - return loginSuccess( - userId, - { idpIntentId: id, idpIntentToken: token }, - requestId, - branding, - ); + return loginSuccess(userId, { idpIntentId: id, idpIntentToken: token }, requestId, branding); } if (link) { @@ -174,12 +168,7 @@ export default async function Page(props: { if (!idpLink) { return linkingFailed(branding); } else { - return linkingSuccess( - userId, - { idpIntentId: id, idpIntentToken: token }, - requestId, - branding, - ); + return linkingSuccess(userId, { idpIntentId: id, idpIntentToken: token }, requestId, branding); } } @@ -230,12 +219,7 @@ export default async function Page(props: { if (!idpLink) { return linkingFailed(branding); } else { - return linkingSuccess( - foundUser.userId, - { idpIntentId: id, idpIntentToken: token }, - requestId, - branding, - ); + return linkingSuccess(foundUser.userId, { idpIntentId: id, idpIntentToken: token }, requestId, branding); } } } @@ -260,10 +244,7 @@ export default async function Page(props: { organization: organizationSchema, }); } else { - addHumanUserWithOrganization = create( - AddHumanUserRequestSchema, - addHumanUser, - ); + addHumanUserWithOrganization = create(AddHumanUserRequestSchema, addHumanUser); } try { @@ -272,16 +253,10 @@ export default async function Page(props: { request: addHumanUserWithOrganization, }); } catch (error: unknown) { - console.error( - "An error occurred while creating the user:", - error, - addHumanUser, - ); + console.error("An error occurred while creating the user:", error, addHumanUser); return loginFailed( branding, - (error as ConnectError).message - ? (error as ConnectError).message - : "Could not create user", + (error as ConnectError).message ? (error as ConnectError).message : "Could not create user", ); } } else if (options?.isCreationAllowed) { @@ -325,11 +300,7 @@ export default async function Page(props: {

- + ); diff --git a/src/app/(login)/idp/page.tsx b/src/app/(login)/idp/page.tsx index ab16e897e5..839efada0b 100644 --- a/src/app/(login)/idp/page.tsx +++ b/src/app/(login)/idp/page.tsx @@ -3,8 +3,15 @@ import { SignInWithIdp } from "@/components/sign-in-with-idp"; import { Translated } from "@/components/translated"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { getActiveIdentityProviders, getBrandingSettings } from "@/lib/zitadel"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("idp"); + return { title: t('title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { @@ -38,7 +45,7 @@ export default async function Page(props: {

- {identityProviders && ( + {!!identityProviders?.length && ( { + const t = await getTranslations("common"); + return { title: t("title") }; +} + +export default async function RootLayout({ children }: { children: ReactNode }) { return ( @@ -58,7 +60,6 @@ export default async function RootLayout({ - ); diff --git a/src/app/(login)/loginname/page.tsx b/src/app/(login)/loginname/page.tsx index 37b57ed1b9..7694e43b19 100644 --- a/src/app/(login)/loginname/page.tsx +++ b/src/app/(login)/loginname/page.tsx @@ -10,8 +10,15 @@ import { getLoginSettings, } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("loginname"); + return { title: t('title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { @@ -78,7 +85,7 @@ export default async function Page(props: { allowRegister={!!loginSettings?.allowRegister} > - {identityProviders && loginSettings?.allowExternalIdp && ( + {loginSettings?.allowExternalIdp && !!identityProviders?.length && (
{ + const t = await getTranslations("logout"); + return { title: t('title')}; +} + async function loadSessions({ serviceUrl }: { serviceUrl: string }) { const cookieIds = await getAllSessionCookieIds(); @@ -26,13 +29,11 @@ async function loadSessions({ serviceUrl }: { serviceUrl: string }) { } } -export default async function Page(props: { - searchParams: Promise>; -}) { +export default async function Page(props: { searchParams: Promise> }) { const searchParams = await props.searchParams; const organization = searchParams?.organization; - const postLogoutRedirectUri = searchParams?.post_logout_redirect_uri; + const postLogoutRedirectUri = searchParams?.post_logout_redirect || searchParams?.post_logout_redirect_uri; const logoutHint = searchParams?.logout_hint; // TODO implement with new translation service // const UILocales = searchParams?.ui_locales; diff --git a/src/app/(login)/mfa/page.tsx b/src/app/(login)/mfa/page.tsx index 5543cdf66f..35af054743 100644 --- a/src/app/(login)/mfa/page.tsx +++ b/src/app/(login)/mfa/page.tsx @@ -12,8 +12,15 @@ import { getSession, listAuthenticationMethodTypes, } from "@/lib/zitadel"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("mfa"); + return { title: t('verify.title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { diff --git a/src/app/(login)/mfa/set/page.tsx b/src/app/(login)/mfa/set/page.tsx index ebfa358d6d..c966a4d881 100644 --- a/src/app/(login)/mfa/set/page.tsx +++ b/src/app/(login)/mfa/set/page.tsx @@ -16,31 +16,34 @@ import { } from "@/lib/zitadel"; import { Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("mfa"); + return { title: t("set.title") }; +} + function isSessionValid(session: Partial): { valid: boolean; verifiedAt?: Timestamp; } { const validPassword = session?.factors?.password?.verifiedAt; const validPasskey = session?.factors?.webAuthN?.verifiedAt; - const stillValid = session.expirationDate - ? timestampDate(session.expirationDate) > new Date() - : true; + const validIDP = session?.factors?.intent?.verifiedAt; + const stillValid = session.expirationDate ? timestampDate(session.expirationDate) > new Date() : true; - const verifiedAt = validPassword || validPasskey; - const valid = !!((validPassword || validPasskey) && stillValid); + const verifiedAt = validPassword || validPasskey || validIDP; + const valid = !!((validPassword || validPasskey || validIDP) && stillValid); return { valid, verifiedAt }; } -export default async function Page(props: { - searchParams: Promise>; -}) { +export default async function Page(props: { searchParams: Promise> }) { const searchParams = await props.searchParams; - const { loginName, checkAfter, force, requestId, organization, sessionId } = - searchParams; + const { loginName, checkAfter, force, requestId, organization, sessionId } = searchParams; const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -61,8 +64,7 @@ export default async function Page(props: { userId, }).then((methods) => { return getUserByID({ serviceUrl, userId }).then((user) => { - const humanUser = - user.user?.type.case === "human" ? user.user?.type.value : undefined; + const humanUser = user.user?.type.case === "human" ? user.user?.type.value : undefined; return { id: session.id, @@ -76,10 +78,7 @@ export default async function Page(props: { }); } - async function loadSessionByLoginname( - loginName?: string, - organization?: string, - ) { + async function loadSessionByLoginname(loginName?: string, organization?: string) { return loadMostRecentSession({ serviceUrl, sessionParams: { @@ -145,24 +144,21 @@ export default async function Page(props: { )} - {isSessionValid(sessionWithData).valid && - loginSettings && - sessionWithData && - sessionWithData.factors?.user?.id && ( - - )} + {valid && loginSettings && sessionWithData && sessionWithData.factors?.user?.id && ( + + )}
diff --git a/src/app/(login)/otp/[method]/page.tsx b/src/app/(login)/otp/[method]/page.tsx index 1b1356315a..4e8bfe8606 100644 --- a/src/app/(login)/otp/[method]/page.tsx +++ b/src/app/(login)/otp/[method]/page.tsx @@ -4,15 +4,19 @@ import { LoginOTP } from "@/components/login-otp"; import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getSessionCookieById } from "@/lib/cookies"; +import { getOriginalHost } from "@/lib/server/host"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; -import { - getBrandingSettings, - getLoginSettings, - getSession, -} from "@/lib/zitadel"; +import { getBrandingSettings, getLoginSettings, getSession } from "@/lib/zitadel"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("otp"); + return { title: t("verify.title") }; +} + export default async function Page(props: { searchParams: Promise>; params: Promise>; @@ -22,11 +26,7 @@ export default async function Page(props: { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const host = await getOriginalHost(); const { loginName, // send from password page @@ -113,9 +113,7 @@ export default async function Page(props: { loginName={loginName ?? session.factors?.user?.loginName} sessionId={sessionId} requestId={requestId} - organization={ - organization ?? session?.factors?.user?.organizationId - } + organization={organization ?? session?.factors?.user?.organizationId} method={method} loginSettings={loginSettings} host={host} diff --git a/src/app/(login)/passkey/page.tsx b/src/app/(login)/passkey/page.tsx index bef71986f3..7a287d5acf 100644 --- a/src/app/(login)/passkey/page.tsx +++ b/src/app/(login)/passkey/page.tsx @@ -7,8 +7,15 @@ import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("passkey"); + return { title: t('verify.title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { diff --git a/src/app/(login)/passkey/set/page.tsx b/src/app/(login)/passkey/set/page.tsx index 52c195e6cf..e8ed802b9f 100644 --- a/src/app/(login)/passkey/set/page.tsx +++ b/src/app/(login)/passkey/set/page.tsx @@ -5,32 +5,57 @@ import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; -import { getBrandingSettings } from "@/lib/zitadel"; +import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; +import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; +import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; -export default async function Page(props: { - searchParams: Promise>; -}) { +export async function generateMetadata(): Promise { + const t = await getTranslations("passkey"); + return { title: t("set.title") }; +} + +export default async function Page(props: { searchParams: Promise> }) { const searchParams = await props.searchParams; - const { loginName, prompt, organization, requestId } = searchParams; + const { userId, loginName, prompt, organization, requestId, code, id } = searchParams; const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const session = await loadMostRecentSession({ - serviceUrl, - sessionParams: { - loginName, - organization, - }, - }); + // also allow no session to be found for userId-based flows + let session: Session | undefined; + if (loginName) { + session = await loadMostRecentSession({ + serviceUrl, + sessionParams: { + loginName, + organization, + }, + }); + } const branding = await getBrandingSettings({ serviceUrl, organization, }); + let user: User | undefined; + let displayName: string | undefined; + if (userId) { + const userResponse = await getUserByID({ + serviceUrl, + userId, + }); + user = userResponse.user; + + if (user?.type.case === "human") { + displayName = (user.type.value as HumanUser).profile?.displayName; + } + } + return (
@@ -38,14 +63,21 @@ export default async function Page(props: { - {session && ( + {session ? ( - )} + ) : user ? ( + + ) : null}

@@ -63,7 +95,7 @@ export default async function Page(props: { - {!session && ( + {!session && !user && (
@@ -71,12 +103,15 @@ export default async function Page(props: {
)} - {session?.id && ( + {(session?.id || userId) && ( )}
diff --git a/src/app/(login)/password/change/page.tsx b/src/app/(login)/password/change/page.tsx index 78ba88d282..690c1dcb3f 100644 --- a/src/app/(login)/password/change/page.tsx +++ b/src/app/(login)/password/change/page.tsx @@ -10,8 +10,15 @@ import { getLoginSettings, getPasswordComplexitySettings, } from "@/lib/zitadel"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("password"); + return { title: t('change.title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { @@ -55,7 +62,7 @@ export default async function Page(props: { )}

- +

{/* show error only if usernames should be shown to be unknown */} diff --git a/src/app/(login)/password/page.tsx b/src/app/(login)/password/page.tsx index a9ab4091f9..97be81e7b8 100644 --- a/src/app/(login)/password/page.tsx +++ b/src/app/(login)/password/page.tsx @@ -11,8 +11,15 @@ import { getLoginSettings, } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("password"); + return { title: t('verify.title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { diff --git a/src/app/(login)/password/set/page.tsx b/src/app/(login)/password/set/page.tsx index c47305929a..738b68df2b 100644 --- a/src/app/(login)/password/set/page.tsx +++ b/src/app/(login)/password/set/page.tsx @@ -13,8 +13,15 @@ import { } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("password"); + return { title: t('set.title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { diff --git a/src/app/(login)/register/page.tsx b/src/app/(login)/register/page.tsx index 221679ef07..2168813c40 100644 --- a/src/app/(login)/register/page.tsx +++ b/src/app/(login)/register/page.tsx @@ -14,11 +14,16 @@ import { } from "@/lib/zitadel"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; -export default async function Page(props: { - searchParams: Promise>; -}) { +export async function generateMetadata(): Promise { + const t = await getTranslations("register"); + return { title: t("title") }; +} + +export default async function Page(props: { searchParams: Promise> }) { const searchParams = await props.searchParams; let { firstname, lastname, email, organization, requestId } = searchParams; @@ -97,12 +102,9 @@ export default async function Page(props: { {legal && passwordComplexitySettings && organization && - (loginSettings.allowUsernamePassword || - loginSettings.passkeysType == PasskeysType.ALLOWED) && ( + (loginSettings.allowUsernamePassword || loginSettings.passkeysType == PasskeysType.ALLOWED) && ( -
-

- -

-
- { + const t = await getTranslations("signedin"); + return { title: t('title', { user: '' })}; +} + async function loadSessionById( serviceUrl: string, sessionId: string, diff --git a/src/app/(login)/u2f/page.tsx b/src/app/(login)/u2f/page.tsx index c54b45103f..558314e5be 100644 --- a/src/app/(login)/u2f/page.tsx +++ b/src/app/(login)/u2f/page.tsx @@ -7,22 +7,22 @@ import { getSessionCookieById } from "@/lib/cookies"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getSession } from "@/lib/zitadel"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; -export default async function Page(props: { - searchParams: Promise>; -}) { +export async function generateMetadata(): Promise { + const t = await getTranslations("u2f"); + return { title: t("verify.title") }; +} + +export default async function Page(props: { searchParams: Promise> }) { const searchParams = await props.searchParams; const { loginName, requestId, sessionId, organization } = searchParams; const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } const branding = await getBrandingSettings({ serviceUrl, @@ -30,17 +30,13 @@ export default async function Page(props: { }); const sessionFactors = sessionId - ? await loadSessionById(serviceUrl, sessionId, organization) + ? await loadSessionById(sessionId, organization) : await loadMostRecentSession({ serviceUrl, sessionParams: { loginName, organization }, }); - async function loadSessionById( - host: string, - sessionId: string, - organization?: string, - ) { + async function loadSessionById(sessionId: string, organization?: string) { const recent = await getSessionCookieById({ sessionId, organization }); return getSession({ serviceUrl, diff --git a/src/app/(login)/u2f/set/page.tsx b/src/app/(login)/u2f/set/page.tsx index 79f64bf67d..455b917a26 100644 --- a/src/app/(login)/u2f/set/page.tsx +++ b/src/app/(login)/u2f/set/page.tsx @@ -6,8 +6,15 @@ import { UserAvatar } from "@/components/user-avatar"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings } from "@/lib/zitadel"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("u2f"); + return { title: t('set.title')}; +} + export default async function Page(props: { searchParams: Promise>; }) { diff --git a/src/app/(login)/verify/page.tsx b/src/app/(login)/verify/page.tsx index 7497698222..e1aa916983 100644 --- a/src/app/(login)/verify/page.tsx +++ b/src/app/(login)/verify/page.tsx @@ -4,17 +4,24 @@ import { Translated } from "@/components/translated"; import { UserAvatar } from "@/components/user-avatar"; import { VerifyForm } from "@/components/verify-form"; import { sendEmailCode, sendInviteEmailCode } from "@/lib/server/verify"; +import { getOriginalHostWithProtocol } from "@/lib/server/host"; import { getServiceUrlFromHeaders } from "@/lib/service-url"; import { loadMostRecentSession } from "@/lib/session"; import { getBrandingSettings, getUserByID } from "@/lib/zitadel"; import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; import { headers } from "next/headers"; +export async function generateMetadata(): Promise { + const t = await getTranslations("verify"); + return { title: t("verify.title") }; +} + export default async function Page(props: { searchParams: Promise }) { const searchParams = await props.searchParams; - const { userId, loginName, code, organization, requestId, invite, send } = - searchParams; + const { userId, loginName, code, organization, requestId, invite, send } = searchParams; const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -34,17 +41,13 @@ export default async function Page(props: { searchParams: Promise }) { const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; async function sendEmail(userId: string) { - const host = _headers.get("host"); - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const hostWithProtocol = await getOriginalHostWithProtocol(); if (invite === "true") { await sendInviteEmailCode({ userId, urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + + `${hostWithProtocol}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + (requestId ? `&requestId=${requestId}` : ""), }).catch((error) => { console.error("Could not send invitation email", error); @@ -54,7 +57,7 @@ export default async function Page(props: { searchParams: Promise }) { await sendEmailCode({ userId, urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + + `${hostWithProtocol}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + (requestId ? `&requestId=${requestId}` : ""), }).catch((error) => { console.error("Could not send verification email", error); @@ -150,11 +153,7 @@ export default async function Page(props: { searchParams: Promise }) { > ) : ( user && ( - + ) )} diff --git a/src/app/login/route.ts b/src/app/login/route.ts index 7b57e1a5e9..ed61c43c3c 100644 --- a/src/app/login/route.ts +++ b/src/app/login/route.ts @@ -1,66 +1,19 @@ import { getAllSessions } from "@/lib/cookies"; -import { idpTypeToSlug } from "@/lib/idp"; -import { loginWithOIDCAndSession } from "@/lib/oidc"; -import { loginWithSAMLAndSession } from "@/lib/saml"; -import { sendLoginname, SendLoginnameCommand } from "@/lib/server/loginname"; -import { constructUrl, getServiceUrlFromHeaders } from "@/lib/service-url"; -import { findValidSession } from "@/lib/session"; -import { - createCallback, - createResponse, - getActiveIdentityProviders, - getAuthRequest, - getOrgsByDomain, - getSAMLRequest, - getSecuritySettings, - listSessions, - startIdentityProviderFlow, -} from "@/lib/zitadel"; -import { create } from "@zitadel/client"; -import { Prompt } from "@zitadel/proto/zitadel/oidc/v2/authorization_pb"; -import { - CreateCallbackRequestSchema, - SessionSchema, -} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; -import { CreateResponseRequestSchema } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb"; +import { getServiceUrlFromHeaders } from "@/lib/service-url"; +import { validateAuthRequest, isRSCRequest } from "@/lib/auth-utils"; +import { handleOIDCFlowInitiation, handleSAMLFlowInitiation, FlowInitiationParams } from "@/lib/server/flow-initiation"; +import { listSessions } from "@/lib/zitadel"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { headers } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; -import { DEFAULT_CSP } from "../../../constants/csp"; export const dynamic = "force-dynamic"; export const revalidate = false; export const fetchCache = "default-no-store"; +// Add this to prevent RSC requests +export const runtime = "nodejs"; -const gotoAccounts = ({ - request, - requestId, - organization, -}: { - request: NextRequest; - requestId: string; - organization?: string; -}): NextResponse => { - const accountsUrl = constructUrl(request, "/accounts"); - - if (requestId) { - accountsUrl.searchParams.set("requestId", requestId); - } - if (organization) { - accountsUrl.searchParams.set("organization", organization); - } - - return NextResponse.redirect(accountsUrl); -}; - -async function loadSessions({ - serviceUrl, - ids, -}: { - serviceUrl: string; - ids: string[]; -}): Promise { +async function loadSessions({ serviceUrl, ids }: { serviceUrl: string; ids: string[] }): Promise { const response = await listSessions({ serviceUrl, ids: ids.filter((id: string | undefined) => !!id), @@ -69,34 +22,21 @@ async function loadSessions({ return response?.sessions ?? []; } -const ORG_SCOPE_REGEX = /urn:zitadel:iam:org:id:([0-9]+)/; -const ORG_DOMAIN_SCOPE_REGEX = /urn:zitadel:iam:org:domain:primary:(.+)/; // TODO: check regex for all domain character options -const IDP_SCOPE_REGEX = /urn:zitadel:iam:org:idp:id:(.+)/; - export async function GET(request: NextRequest) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); const searchParams = request.nextUrl.searchParams; - const oidcRequestId = searchParams.get("authRequest"); // oidc initiated request - const samlRequestId = searchParams.get("samlRequest"); // saml initiated request - - // internal request id which combines authRequest and samlRequest with the prefix oidc_ or saml_ - let requestId = - searchParams.get("requestId") ?? - (oidcRequestId - ? `oidc_${oidcRequestId}` - : samlRequestId - ? `saml_${samlRequestId}` - : undefined); - - const sessionId = searchParams.get("sessionId"); + // Defensive check: block RSC requests early + if (isRSCRequest(searchParams)) { + return NextResponse.json({ error: "RSC requests not supported" }, { status: 400 }); + } - // TODO: find a better way to handle _rsc (react server components) requests and block them to avoid conflicts when creating oidc callback - const _rsc = searchParams.get("_rsc"); - if (_rsc) { - return NextResponse.json({ error: "No _rsc supported" }, { status: 500 }); + // Early validation: if no valid request parameters, return error immediately + const requestId = validateAuthRequest(searchParams); + if (!requestId) { + return NextResponse.json({ error: "No valid authentication request found" }, { status: 400 }); } const sessionCookies = await getAllSessions(); @@ -106,460 +46,23 @@ export async function GET(request: NextRequest) { sessions = await loadSessions({ serviceUrl, ids }); } - // complete flow if session and request id are provided - if (requestId && sessionId) { - if (requestId.startsWith("oidc_")) { - // this finishes the login process for OIDC - return loginWithOIDCAndSession({ - serviceUrl, - authRequest: requestId.replace("oidc_", ""), - sessionId, - sessions, - sessionCookies, - request, - }); - } else if (requestId.startsWith("saml_")) { - // this finishes the login process for SAML - return loginWithSAMLAndSession({ - serviceUrl, - samlRequest: requestId.replace("saml_", ""), - sessionId, - sessions, - sessionCookies, - request, - }); - } - } - - // continue with OIDC - if (requestId && requestId.startsWith("oidc_")) { - const { authRequest } = await getAuthRequest({ - serviceUrl, - authRequestId: requestId.replace("oidc_", ""), - }); - - let organization = ""; - let suffix = ""; - let idpId = ""; - - if (authRequest?.scope) { - const orgScope = authRequest.scope.find((s: string) => - ORG_SCOPE_REGEX.test(s), - ); - - const idpScope = authRequest.scope.find((s: string) => - IDP_SCOPE_REGEX.test(s), - ); - - if (orgScope) { - const matched = ORG_SCOPE_REGEX.exec(orgScope); - organization = matched?.[1] ?? ""; - } else { - const orgDomainScope = authRequest.scope.find((s: string) => - ORG_DOMAIN_SCOPE_REGEX.test(s), - ); - - if (orgDomainScope) { - const matched = ORG_DOMAIN_SCOPE_REGEX.exec(orgDomainScope); - const orgDomain = matched?.[1] ?? ""; - if (orgDomain) { - const orgs = await getOrgsByDomain({ - serviceUrl, - domain: orgDomain, - }); - if (orgs.result && orgs.result.length === 1) { - organization = orgs.result[0].id ?? ""; - suffix = orgDomain; - } - } - } - } - - if (idpScope) { - const matched = IDP_SCOPE_REGEX.exec(idpScope); - idpId = matched?.[1] ?? ""; - - const identityProviders = await getActiveIdentityProviders({ - serviceUrl, - orgId: organization ? organization : undefined, - }).then((resp) => { - return resp.identityProviders; - }); - - const idp = identityProviders.find((idp) => idp.id === idpId); - - if (idp) { - const origin = request.nextUrl.origin; - - const identityProviderType = identityProviders[0].type; - - if (identityProviderType === IdentityProviderType.LDAP) { - const ldapUrl = constructUrl(request, "/ldap"); - if (authRequest.id) { - ldapUrl.searchParams.set("requestId", `oidc_${authRequest.id}`); - } - if (organization) { - ldapUrl.searchParams.set("organization", organization); - } - - return NextResponse.redirect(ldapUrl); - } - - let provider = idpTypeToSlug(identityProviderType); - - const params = new URLSearchParams(); - - if (requestId) { - params.set("requestId", requestId); - } - - if (organization) { - params.set("organization", organization); - } - - let url: string | null = await startIdentityProviderFlow({ - serviceUrl, - idpId, - urls: { - successUrl: - `${origin}/idp/${provider}/success?` + - new URLSearchParams(params), - failureUrl: - `${origin}/idp/${provider}/failure?` + - new URLSearchParams(params), - }, - }); - - if (!url) { - return NextResponse.json( - { error: "Could not start IDP flow" }, - { status: 500 }, - ); - } - - if (url.startsWith("/")) { - // if the url is a relative path, construct the absolute url - url = constructUrl(request, url).toString(); - } - - return NextResponse.redirect(url); - } - } - } - - if (authRequest && authRequest.prompt.includes(Prompt.CREATE)) { - const registerUrl = constructUrl(request, "/register"); - if (authRequest.id) { - registerUrl.searchParams.set("requestId", `oidc_${authRequest.id}`); - } - if (organization) { - registerUrl.searchParams.set("organization", organization); - } - - return NextResponse.redirect(registerUrl); - } - - // use existing session and hydrate it for oidc - if (authRequest && sessions.length) { - // if some accounts are available for selection and select_account is set - if (authRequest.prompt.includes(Prompt.SELECT_ACCOUNT)) { - return gotoAccounts({ - request, - requestId: `oidc_${authRequest.id}`, - organization, - }); - } else if (authRequest.prompt.includes(Prompt.LOGIN)) { - /** - * The login prompt instructs the authentication server to prompt the user for re-authentication, regardless of whether the user is already authenticated - */ - - // if a hint is provided, skip loginname page and jump to the next page - if (authRequest.loginHint) { - try { - let command: SendLoginnameCommand = { - loginName: authRequest.loginHint, - requestId: authRequest.id, - }; - - if (organization) { - command = { ...command, organization }; - } - - const res = await sendLoginname(command); - - if (res && "redirect" in res && res?.redirect) { - const absoluteUrl = constructUrl(request, res.redirect); - return NextResponse.redirect(absoluteUrl.toString()); - } - } catch (error) { - console.error("Failed to execute sendLoginname:", error); - } - } - - const loginNameUrl = constructUrl(request, "/loginname"); - if (authRequest.id) { - loginNameUrl.searchParams.set("requestId", `oidc_${authRequest.id}`); - } - if (authRequest.loginHint) { - loginNameUrl.searchParams.set("loginName", authRequest.loginHint); - } - if (organization) { - loginNameUrl.searchParams.set("organization", organization); - } - if (suffix) { - loginNameUrl.searchParams.set("suffix", suffix); - } - return NextResponse.redirect(loginNameUrl); - } else if (authRequest.prompt.includes(Prompt.NONE)) { - /** - * With an OIDC none prompt, the authentication server must not display any authentication or consent user interface pages. - * This means that the user should not be prompted to enter their password again. - * Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction - **/ - const securitySettings = await getSecuritySettings({ - serviceUrl, - }); - - const selectedSession = await findValidSession({ - serviceUrl, - sessions, - authRequest, - }); - - const noSessionResponse = NextResponse.json( - { error: "No active session found" }, - { status: 400 }, - ); - - if (securitySettings?.embeddedIframe?.enabled) { - securitySettings.embeddedIframe.allowedOrigins; - noSessionResponse.headers.set( - "Content-Security-Policy", - `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, - ); - noSessionResponse.headers.delete("X-Frame-Options"); - } - - if (!selectedSession || !selectedSession.id) { - return noSessionResponse; - } - - const cookie = sessionCookies.find( - (cookie) => cookie.id === selectedSession.id, - ); - - if (!cookie || !cookie.id || !cookie.token) { - return noSessionResponse; - } - - const session = { - sessionId: cookie.id, - sessionToken: cookie.token, - }; - - const { callbackUrl } = await createCallback({ - serviceUrl, - req: create(CreateCallbackRequestSchema, { - authRequestId: requestId.replace("oidc_", ""), - callbackKind: { - case: "session", - value: create(SessionSchema, session), - }, - }), - }); - - const callbackResponse = NextResponse.redirect(callbackUrl); - - if (securitySettings?.embeddedIframe?.enabled) { - securitySettings.embeddedIframe.allowedOrigins; - callbackResponse.headers.set( - "Content-Security-Policy", - `${DEFAULT_CSP} frame-ancestors ${securitySettings.embeddedIframe.allowedOrigins.join(" ")};`, - ); - callbackResponse.headers.delete("X-Frame-Options"); - } - - return callbackResponse; - } else { - // check for loginHint, userId hint and valid sessions - let selectedSession = await findValidSession({ - serviceUrl, - sessions, - authRequest, - }); - - if (!selectedSession || !selectedSession.id) { - return gotoAccounts({ - request, - requestId: `oidc_${authRequest.id}`, - organization, - }); - } - - const cookie = sessionCookies.find( - (cookie) => cookie.id === selectedSession.id, - ); - - if (!cookie || !cookie.id || !cookie.token) { - return gotoAccounts({ - request, - requestId: `oidc_${authRequest.id}`, - organization, - }); - } - - const session = { - sessionId: cookie.id, - sessionToken: cookie.token, - }; - - try { - const { callbackUrl } = await createCallback({ - serviceUrl, - req: create(CreateCallbackRequestSchema, { - authRequestId: requestId.replace("oidc_", ""), - callbackKind: { - case: "session", - value: create(SessionSchema, session), - }, - }), - }); - if (callbackUrl) { - return NextResponse.redirect(callbackUrl); - } else { - console.log( - "could not create callback, redirect user to choose other account", - ); - return gotoAccounts({ - request, - organization, - requestId: `oidc_${authRequest.id}`, - }); - } - } catch (error) { - console.error(error); - return gotoAccounts({ - request, - requestId: `oidc_${authRequest.id}`, - organization, - }); - } - } - } else { - const loginNameUrl = constructUrl(request, "/loginname"); - - loginNameUrl.searchParams.set("requestId", requestId); - if (authRequest?.loginHint) { - loginNameUrl.searchParams.set("loginName", authRequest.loginHint); - loginNameUrl.searchParams.set("submit", "true"); // autosubmit - } - - if (organization) { - loginNameUrl.searchParams.append("organization", organization); - // loginNameUrl.searchParams.set("organization", organization); - } - - return NextResponse.redirect(loginNameUrl); - } - } - // continue with SAML - else if (requestId && requestId.startsWith("saml_")) { - const { samlRequest } = await getSAMLRequest({ - serviceUrl, - samlRequestId: requestId.replace("saml_", ""), - }); - - if (!samlRequest) { - return NextResponse.json( - { error: "No samlRequest found" }, - { status: 400 }, - ); - } - - let selectedSession = await findValidSession({ - serviceUrl, - sessions, - samlRequest, - }); - - if (!selectedSession || !selectedSession.id) { - return gotoAccounts({ - request, - requestId: `saml_${samlRequest.id}`, - }); - } - - const cookie = sessionCookies.find( - (cookie) => cookie.id === selectedSession.id, - ); - - if (!cookie || !cookie.id || !cookie.token) { - return gotoAccounts({ - request, - requestId: `saml_${samlRequest.id}`, - // organization, - }); - } - - const session = { - sessionId: cookie.id, - sessionToken: cookie.token, - }; - - try { - const { url, binding } = await createResponse({ - serviceUrl, - req: create(CreateResponseRequestSchema, { - samlRequestId: requestId.replace("saml_", ""), - responseKind: { - case: "session", - value: session, - }, - }), - }); - if (url && binding.case === "redirect") { - return NextResponse.redirect(url); - } else if (url && binding.case === "post") { - // Create HTML form that auto-submits via POST and escape the SAML cookie - const html = ` - - -
- - - -
- - - `; - - return new NextResponse(html, { - headers: { "Content-Type": "text/html" }, - }); - } else { - console.log( - "could not create response, redirect user to choose other account", - ); - return gotoAccounts({ - request, - requestId: `saml_${samlRequest.id}`, - }); - } - } catch (error) { - console.error(error); - return gotoAccounts({ - request, - requestId: `saml_${samlRequest.id}`, - }); - } - } - // Device Authorization does not need to start here as it is handled on the /device endpoint - else { - return NextResponse.json( - { error: "No authRequest nor samlRequest provided" }, - { status: 500 }, - ); + // Flow initiation - delegate to appropriate handler + const flowParams: FlowInitiationParams = { + serviceUrl, + requestId, + sessions, + sessionCookies, + request, + }; + + if (requestId.startsWith("oidc_")) { + return handleOIDCFlowInitiation(flowParams); + } else if (requestId.startsWith("saml_")) { + return handleSAMLFlowInitiation(flowParams); + } else if (requestId.startsWith("device_")) { + // Device Authorization does not need to start here as it is handled on the /device endpoint + return NextResponse.json({ error: "Device authorization should use /device endpoint" }, { status: 400 }); + } else { + return NextResponse.json({ error: "Invalid request ID format" }, { status: 400 }); } } diff --git a/src/components/app-avatar.tsx b/src/components/app-avatar.tsx index 84879d08ac..8686043fa0 100644 --- a/src/components/app-avatar.tsx +++ b/src/components/app-avatar.tsx @@ -1,6 +1,5 @@ import { ColorShade, getColorHash } from "@/helpers/colors"; import { useTheme } from "next-themes"; -import Image from "next/image"; import { getInitials } from "./avatar"; interface AvatarProps { @@ -33,7 +32,7 @@ export function AppAvatar({ appName, imageUrl, shadow }: AvatarProps) { style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark} > {imageUrl ? ( - avatar {imageUrl ? ( - avatar ) : ( - - {credentials} - + {credentials} )}
); diff --git a/src/components/change-password-form.tsx b/src/components/change-password-form.tsx index 6124812e27..394d0a6634 100644 --- a/src/components/change-password-form.tsx +++ b/src/components/change-password-form.tsx @@ -161,7 +161,7 @@ export function ChangePasswordForm({ {...register("password", { required: t("change.required.newPassword"), })} - label="New Password" + label={t("change.labels.newPassword")} error={errors.password?.message as string} data-testid="password-change-text-input" /> @@ -174,7 +174,7 @@ export function ChangePasswordForm({ {...register("confirmPassword", { required: t("change.required.confirmPassword"), })} - label="Confirm Password" + label={t("change.labels.confirmPassword")} error={errors.confirmPassword?.message as string} data-testid="password-change-confirm-text-input" /> diff --git a/src/components/choose-second-factor-to-setup.tsx b/src/components/choose-second-factor-to-setup.tsx index 38599053ff..d636cb722f 100644 --- a/src/components/choose-second-factor-to-setup.tsx +++ b/src/components/choose-second-factor-to-setup.tsx @@ -1,14 +1,13 @@ "use client"; import { skipMFAAndContinueWithNextUrl } from "@/lib/server/session"; -import { - LoginSettings, - SecondFactorType, -} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { LoginSettings, SecondFactorType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { useRouter } from "next/navigation"; import { EMAIL, SMS, TOTP, U2F } from "./auth-methods"; import { Translated } from "./translated"; +import { useState } from "react"; +import { Alert } from "./alert"; type Props = { userId: string; @@ -40,6 +39,8 @@ export function ChooseSecondFactorToSetup({ const router = useRouter(); const params = new URLSearchParams({}); + const [error, setError] = useState(""); + if (loginName) { params.append("loginName", loginName); } @@ -62,31 +63,15 @@ export function ChooseSecondFactorToSetup({ {loginSettings.secondFactors.map((factor) => { switch (factor) { case SecondFactorType.OTP: - return TOTP( - userMethods.includes(AuthenticationMethodType.TOTP), - "/otp/time-based/set?" + params, - ); + return TOTP(userMethods.includes(AuthenticationMethodType.TOTP), "/otp/time-based/set?" + params); case SecondFactorType.U2F: - return U2F( - userMethods.includes(AuthenticationMethodType.U2F), - "/u2f/set?" + params, - ); + return U2F(userMethods.includes(AuthenticationMethodType.U2F), "/u2f/set?" + params); case SecondFactorType.OTP_EMAIL: return ( - emailVerified && - EMAIL( - userMethods.includes(AuthenticationMethodType.OTP_EMAIL), - "/otp/email/set?" + params, - ) + emailVerified && EMAIL(userMethods.includes(AuthenticationMethodType.OTP_EMAIL), "/otp/email/set?" + params) ); case SecondFactorType.OTP_SMS: - return ( - phoneVerified && - SMS( - userMethods.includes(AuthenticationMethodType.OTP_SMS), - "/otp/sms/set?" + params, - ) - ); + return phoneVerified && SMS(userMethods.includes(AuthenticationMethodType.OTP_SMS), "/otp/sms/set?" + params); default: return null; } @@ -96,7 +81,7 @@ export function ChooseSecondFactorToSetup({ )} + {error && ( +
+ {error} +
+ )} ); } diff --git a/src/components/device-code-form.tsx b/src/components/device-code-form.tsx index 9c5a28ca4f..108c1a3529 100644 --- a/src/components/device-code-form.tsx +++ b/src/components/device-code-form.tsx @@ -66,7 +66,7 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) { type="text" autoComplete="one-time-code" {...register("userCode", { required: t("usercode.required.code") })} - label="Code" + label={t("usercode.labels.code")} data-testid="code-text-input" />
@@ -89,7 +89,7 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) { data-testid="submit-button" > {loading && }{" "} - + diff --git a/src/components/idp-signin.tsx b/src/components/idp-signin.tsx index a7c938e90c..ec7da3fc8b 100644 --- a/src/components/idp-signin.tsx +++ b/src/components/idp-signin.tsx @@ -1,8 +1,8 @@ "use client"; -import { createNewSessionFromIdpIntent } from "@/lib/server/idp"; +import { CreateNewSessionCommand, createNewSessionFromIdpIntent } from "@/lib/server/idp"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { Alert } from "./alert"; import { Spinner } from "./spinner"; @@ -16,25 +16,33 @@ type Props = { requestId?: string; }; -export function IdpSignin({ - userId, - idpIntent: { idpIntentId, idpIntentToken }, - requestId, -}: Props) { +export function IdpSignin({ userId, idpIntent: { idpIntentId, idpIntentToken }, requestId }: Props) { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const executedRef = useRef(false); const router = useRouter(); useEffect(() => { - createNewSessionFromIdpIntent({ + // Prevent double execution in React Strict Mode + if (executedRef.current) { + return; + } + + executedRef.current = true; + let request: CreateNewSessionCommand = { userId, idpIntent: { idpIntentId, idpIntentToken, }, - requestId, - }) + }; + + if (requestId) { + request = { ...request, requestId: requestId }; + } + + createNewSessionFromIdpIntent(request) .then((response) => { if (response && "error" in response && response?.error) { setError(response?.error); diff --git a/src/components/ldap-username-password-form.tsx b/src/components/ldap-username-password-form.tsx index cc9071875f..476d7b13e0 100644 --- a/src/components/ldap-username-password-form.tsx +++ b/src/components/ldap-username-password-form.tsx @@ -69,7 +69,7 @@ export function LDAPUsernamePasswordForm({ idpId, link }: Props) { type="text" autoComplete="username" {...register("loginName", { required: t("required.username") })} - label={t("username")} + label={t("labels.username")} data-testid="username-text-input" /> @@ -78,7 +78,7 @@ export function LDAPUsernamePasswordForm({ idpId, link }: Props) { type="password" autoComplete="password" {...register("password", { required: t("required.password") })} - label={t("password")} + label={t("labels.password")} data-testid="password-text-input" /> diff --git a/src/components/login-otp.tsx b/src/components/login-otp.tsx index ffaa5320b0..d4fa0fccbb 100644 --- a/src/components/login-otp.tsx +++ b/src/components/login-otp.tsx @@ -1,6 +1,6 @@ "use client"; -import { getNextUrl } from "@/lib/client"; +import { completeFlowOrGetUrl } from "@/lib/client"; import { updateSession } from "@/lib/server/session"; import { create } from "@zitadel/client"; import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; @@ -33,16 +33,7 @@ type Inputs = { code: string; }; -export function LoginOTP({ - host, - loginName, - sessionId, - requestId, - organization, - method, - code, - loginSettings, -}: Props) { +export function LoginOTP({ host, loginName, sessionId, requestId, organization, method, code, loginSettings }: Props) { const t = useTranslations("otp"); const [error, setError] = useState(""); @@ -190,29 +181,33 @@ export function LoginOTP({ // Wait for 2 seconds to avoid eventual consistency issues with an OTP code being verified in the /login endpoint await new Promise((resolve) => setTimeout(resolve, 2000)); - const url = - requestId && response.sessionId - ? await getNextUrl( - { + // Use unified approach that handles both OIDC/SAML and regular flows + if (response.factors?.user) { + const callbackResponse = await completeFlowOrGetUrl( + requestId && response.sessionId + ? { sessionId: response.sessionId, requestId: requestId, organization: response.factors?.user?.organizationId, + } + : { + loginName: response.factors.user.loginName, + organization: response.factors?.user?.organizationId, }, - loginSettings?.defaultRedirectUri, - ) - : response.factors?.user - ? await getNextUrl( - { - loginName: response.factors.user.loginName, - organization: response.factors?.user?.organizationId, - }, - loginSettings?.defaultRedirectUri, - ) - : null; + loginSettings?.defaultRedirectUri, + ); + setLoading(false); - setLoading(false); - if (url) { - router.push(url); + if ("error" in callbackResponse) { + setError(callbackResponse.error); + return; + } + + if ("redirect" in callbackResponse) { + return router.push(callbackResponse.redirect); + } + } else { + setLoading(false); } } }); @@ -253,7 +248,7 @@ export function LoginOTP({ @@ -278,8 +273,7 @@ export function LoginOTP({ })} data-testid="submit-button" > - {loading && }{" "} - + {loading && } diff --git a/src/components/logo.tsx b/src/components/logo.tsx index 09819f2ac3..b56d7a0a6f 100644 --- a/src/components/logo.tsx +++ b/src/components/logo.tsx @@ -1,5 +1,3 @@ -import Image from "next/image"; - type Props = { darkSrc?: string; lightSrc?: string; @@ -12,24 +10,12 @@ export function Logo({ lightSrc, darkSrc, height = 40, width = 147.5 }: Props) { <> {darkSrc && (
- logo + logo
)} {lightSrc && (
- logo + logo
)} diff --git a/src/components/password-form.tsx b/src/components/password-form.tsx index c8a10a9907..1b4c73b950 100644 --- a/src/components/password-form.tsx +++ b/src/components/password-form.tsx @@ -26,16 +26,11 @@ type Props = { requestId?: string; }; -export function PasswordForm({ - loginSettings, - loginName, - organization, - requestId, -}: Props) { +export function PasswordForm({ loginSettings, loginName, organization, requestId }: Props) { const { register, handleSubmit, formState } = useForm({ mode: "onBlur", }); - + const t = useTranslations("password"); const [info, setInfo] = useState(""); @@ -57,8 +52,9 @@ export function PasswordForm({ }), requestId, }) - .catch(() => { + .catch((error) => { setError("Could not verify password"); + console.error("Error verifying password:", error); return; }) .finally(() => { @@ -122,7 +118,7 @@ export function PasswordForm({ type="password" autoComplete="password" {...register("password", { required: t("verify.required.password") })} - label="Password" + label={t("verify.labels.password")} data-testid="password-text-input" /> {!loginSettings?.hidePasswordReset && ( @@ -137,14 +133,7 @@ export function PasswordForm({ )} - {loginName && ( - - )} + {loginName && } {info && ( @@ -170,8 +159,7 @@ export function PasswordForm({ onClick={handleSubmit(submitPassword)} data-testid="submit-button" > - {loading && }{" "} - + {loading && } diff --git a/src/components/privacy-policy-checkboxes.tsx b/src/components/privacy-policy-checkboxes.tsx index 86ccb08721..b3d784cb9e 100644 --- a/src/components/privacy-policy-checkboxes.tsx +++ b/src/components/privacy-policy-checkboxes.tsx @@ -21,6 +21,18 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) { privacyPolicyAccepted: false, }); + // Helper function to check if all required checkboxes are accepted + const checkAllAccepted = (newState: AcceptanceState) => { + const hasTosLink = !!legal?.tosLink; + const hasPrivacyLink = !!legal?.privacyPolicyLink; + + // Check that all required checkboxes are accepted + return ( + (!hasTosLink || newState.tosAccepted) && + (!hasPrivacyLink || newState.privacyPolicyAccepted) + ); + }; + return ( <>

@@ -50,16 +62,17 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {

{ - setAcceptanceState({ + const newState = { ...acceptanceState, tosAccepted: checked, - }); - onChange(checked && acceptanceState.privacyPolicyAccepted); + }; + setAcceptanceState(newState); + onChange(checkAllAccepted(newState)); }} - data-testid="privacy-policy-checkbox" + data-testid="tos-checkbox" />
@@ -75,25 +88,22 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
{ - setAcceptanceState({ + const newState = { ...acceptanceState, privacyPolicyAccepted: checked, - }); - onChange(checked && acceptanceState.tosAccepted); + }; + setAcceptanceState(newState); + onChange(checkAllAccepted(newState)); }} - data-testid="tos-checkbox" + data-testid="privacy-policy-checkbox" />

- +

diff --git a/src/components/register-form-idp-incomplete.tsx b/src/components/register-form-idp-incomplete.tsx index 10e766be76..d6bd128bb7 100644 --- a/src/components/register-form-idp-incomplete.tsx +++ b/src/components/register-form-idp-incomplete.tsx @@ -1,7 +1,6 @@ "use client"; import { registerUserAndLinkToIDP } from "@/lib/server/register"; -import { useRouter } from "next/navigation"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { FieldValues, useForm } from "react-hook-form"; @@ -60,8 +59,6 @@ export function RegisterFormIDPIncomplete({ const [loading, setLoading] = useState(false); const [error, setError] = useState(""); - const router = useRouter(); - async function submitAndRegister(values: Inputs) { setLoading(true); const response = await registerUserAndLinkToIDP({ @@ -88,11 +85,7 @@ export function RegisterFormIDPIncomplete({ return; } - if (response && "redirect" in response && response.redirect) { - return router.push(response.redirect); - } - - return response; + // If no error, the function has already handled the redirect } const { errors } = formState; @@ -106,7 +99,7 @@ export function RegisterFormIDPIncomplete({ autoComplete="firstname" required {...register("firstname", { required: t("required.firstname") })} - label="First name" + label={t("labels.firstname")} error={errors.firstname?.message as string} data-testid="firstname-text-input" /> @@ -117,7 +110,7 @@ export function RegisterFormIDPIncomplete({ autoComplete="lastname" required {...register("lastname", { required: t("required.lastname") })} - label="Last name" + label={t("labels.lastname")} error={errors.lastname?.message as string} data-testid="lastname-text-input" /> @@ -128,7 +121,7 @@ export function RegisterFormIDPIncomplete({ autoComplete="email" required {...register("email", { required: t("required.email") })} - label="E-mail" + label={t("labels.email")} error={errors.email?.message as string} data-testid="email-text-input" /> @@ -150,8 +143,7 @@ export function RegisterFormIDPIncomplete({ onClick={handleSubmit(submitAndRegister)} data-testid="submit-button" > - {loading && }{" "} - + {loading && }
diff --git a/src/components/register-form.tsx b/src/components/register-form.tsx index 7b4254b0b3..a8f8b0962b 100644 --- a/src/components/register-form.tsx +++ b/src/components/register-form.tsx @@ -2,20 +2,13 @@ import { registerUser } from "@/lib/server/register"; import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb"; -import { - LoginSettings, - PasskeysType, -} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { LoginSettings, PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { useTranslations } from "next-intl"; import { FieldValues, useForm } from "react-hook-form"; import { Alert, AlertType } from "./alert"; -import { - AuthenticationMethod, - AuthenticationMethodRadio, - methods, -} from "./authentication-method-radio"; +import { AuthenticationMethod, AuthenticationMethodRadio, methods } from "./authentication-method-radio"; import { BackButton } from "./back-button"; import { Button, ButtonVariants } from "./button"; import { TextInput } from "./input"; @@ -98,10 +91,7 @@ export function RegisterForm({ return response; } - async function submitAndContinue( - value: Inputs, - withPassword: boolean = false, - ) { + async function submitAndContinue(value: Inputs, withPassword: boolean = false) { const registerParams: any = value; if (organization) { @@ -114,9 +104,7 @@ export function RegisterForm({ // redirect user to /register/password if password is chosen if (withPassword) { - return router.push( - `/register/password?` + new URLSearchParams(registerParams), - ); + return router.push(`/register/password?` + new URLSearchParams(registerParams)); } else { return submitAndRegister(value); } @@ -125,6 +113,11 @@ export function RegisterForm({ const { errors } = formState; const [tosAndPolicyAccepted, setTosAndPolicyAccepted] = useState(false); + + // Check if legal acceptance is required + const isLegalAcceptanceRequired = !!(legal?.tosLink || legal?.privacyPolicyLink); + const canSubmit = formState.isValid && (!isLegalAcceptanceRequired || tosAndPolicyAccepted); + return (
@@ -134,7 +127,7 @@ export function RegisterForm({ autoComplete="firstname" required {...register("firstname", { required: t("required.firstname") })} - label="First name" + label={t("labels.firstname")} error={errors.firstname?.message as string} data-testid="firstname-text-input" /> @@ -145,7 +138,7 @@ export function RegisterForm({ autoComplete="lastname" required {...register("lastname", { required: t("required.lastname") })} - label="Last name" + label={t("labels.lastname")} error={errors.lastname?.message as string} data-testid="lastname-text-input" /> @@ -156,44 +149,33 @@ export function RegisterForm({ autoComplete="email" required {...register("email", { required: t("required.email") })} - label="E-mail" + label={t("labels.email")} error={errors.email?.message as string} data-testid="email-text-input" />
- {legal && ( - + {(legal?.tosLink || legal?.privacyPolicyLink) && ( + )} {/* show chooser if both methods are allowed */} - {loginSettings && - loginSettings.allowUsernamePassword && - loginSettings.passkeysType == PasskeysType.ALLOWED && ( - <> -

- -

- -
- -
- - )} + {loginSettings && loginSettings.allowUsernamePassword && loginSettings.passkeysType == PasskeysType.ALLOWED && ( + <> +

+ +

+ +
+ +
+ + )} {!loginSettings?.allowUsernamePassword && loginSettings?.passkeysType !== PasskeysType.ALLOWED && (!loginSettings?.allowExternalIdp || !idpCount) && (
- +
)} @@ -209,11 +191,10 @@ export function RegisterForm({
diff --git a/src/components/register-u2f.tsx b/src/components/register-u2f.tsx index ebaabb8390..44276304b1 100644 --- a/src/components/register-u2f.tsx +++ b/src/components/register-u2f.tsx @@ -1,7 +1,7 @@ "use client"; import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64"; -import { getNextUrl } from "@/lib/client"; +import { completeFlowOrGetUrl } from "@/lib/client"; import { addU2F, verifyU2F } from "@/lib/server/u2f"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; @@ -22,26 +22,14 @@ type Props = { loginSettings?: LoginSettings; }; -export function RegisterU2f({ - loginName, - sessionId, - organization, - requestId, - checkAfter, - loginSettings, -}: Props) { +export function RegisterU2f({ loginName, sessionId, organization, requestId, checkAfter, loginSettings }: Props) { const [error, setError] = useState(""); const [loading, setLoading] = useState(false); const router = useRouter(); - async function submitVerify( - u2fId: string, - passkeyName: string, - publicKeyCredential: any, - sessionId: string, - ) { + async function submitVerify(u2fId: string, passkeyName: string, publicKeyCredential: any, sessionId: string) { setError(""); setLoading(true); const response = await verifyU2F({ @@ -94,24 +82,14 @@ export function RegisterU2f({ const u2fId = u2fResponse.u2fId; const options: CredentialCreationOptions = - (u2fResponse?.publicKeyCredentialCreationOptions as CredentialCreationOptions) ?? - {}; + (u2fResponse?.publicKeyCredentialCreationOptions as CredentialCreationOptions) ?? {}; if (options.publicKey) { - options.publicKey.challenge = coerceToArrayBuffer( - options.publicKey.challenge, - "challenge", - ); - options.publicKey.user.id = coerceToArrayBuffer( - options.publicKey.user.id, - "userid", - ); + options.publicKey.challenge = coerceToArrayBuffer(options.publicKey.challenge, "challenge"); + options.publicKey.user.id = coerceToArrayBuffer(options.publicKey.user.id, "userid"); if (options.publicKey.excludeCredentials) { options.publicKey.excludeCredentials.map((cred: any) => { - cred.id = coerceToArrayBuffer( - cred.id as string, - "excludeCredentials.id", - ); + cred.id = coerceToArrayBuffer(cred.id as string, "excludeCredentials.id"); return cred; }); } @@ -137,10 +115,7 @@ export function RegisterU2f({ rawId: coerceToBase64Url(rawId, "rawId"), type: resp.type, response: { - attestationObject: coerceToBase64Url( - attestationObject, - "attestationObject", - ), + attestationObject: coerceToBase64Url(attestationObject, "attestationObject"), clientDataJSON: coerceToBase64Url(clientDataJSON, "clientDataJSON"), }, }; @@ -170,27 +145,41 @@ export function RegisterU2f({ return router.push(`/u2f?` + paramsToContinue); } else { - const url = - requestId && sessionId - ? await getNextUrl( - { - sessionId: sessionId, - requestId: requestId, - organization: organization, - }, - loginSettings?.defaultRedirectUri, - ) - : loginName - ? await getNextUrl( - { - loginName: loginName, - organization: organization, - }, - loginSettings?.defaultRedirectUri, - ) - : null; - if (url) { - return router.push(url); + if (requestId && sessionId) { + const callbackResponse = await completeFlowOrGetUrl( + { + sessionId: sessionId, + requestId: requestId, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ); + + if ("error" in callbackResponse) { + setError(callbackResponse.error); + return; + } + + if ("redirect" in callbackResponse) { + return router.push(callbackResponse.redirect); + } + } else if (loginName) { + const callbackResponse = await completeFlowOrGetUrl( + { + loginName: loginName, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ); + + if ("error" in callbackResponse) { + setError(callbackResponse.error); + return; + } + + if ("redirect" in callbackResponse) { + return router.push(callbackResponse.redirect); + } } } } @@ -216,8 +205,7 @@ export function RegisterU2f({ onClick={submitRegisterAndContinue} data-testid="submit-button" > - {loading && }{" "} - + {loading && }
diff --git a/src/components/session-item.tsx b/src/components/session-item.tsx index 41948282a1..c74f52ff1f 100644 --- a/src/components/session-item.tsx +++ b/src/components/session-item.tsx @@ -1,7 +1,7 @@ "use client"; import { sendLoginname } from "@/lib/server/loginname"; -import { clearSession, continueWithSession } from "@/lib/server/session"; +import { clearSession, continueWithSession, ContinueWithSessionCommand } from "@/lib/server/session"; import { XCircleIcon } from "@heroicons/react/24/outline"; import * as Tooltip from "@radix-ui/react-tooltip"; import { Timestamp, timestampDate } from "@zitadel/client"; @@ -21,9 +21,7 @@ export function isSessionValid(session: Partial): { const validPasskey = session?.factors?.webAuthN?.verifiedAt; const validIDP = session?.factors?.intent?.verifiedAt; - const stillValid = session.expirationDate - ? timestampDate(session.expirationDate) > new Date() - : true; + const stillValid = session.expirationDate ? timestampDate(session.expirationDate) > new Date() : true; const verifiedAt = validPassword || validPasskey || validIDP; const valid = !!((validPassword || validPasskey || validIDP) && stillValid); @@ -31,15 +29,7 @@ export function isSessionValid(session: Partial): { return { valid, verifiedAt }; } -export function SessionItem({ - session, - reload, - requestId, -}: { - session: Session; - reload: () => void; - requestId?: string; -}) { +export function SessionItem({ session, reload, requestId }: { session: Session; reload: () => void; requestId?: string }) { const currentLocale = useLocale(); moment.locale(currentLocale === "zh" ? "zh-cn" : currentLocale); @@ -73,13 +63,20 @@ export function SessionItem({ + + + + + `; + + return new NextResponse(html, { + headers: { "Content-Type": "text/html" }, + }); + } + } catch (error) { + console.error("SAML createResponse failed:", error); + } + + // Final fallback: SAML response creation failed - show account selection + return gotoAccounts({ + request, + requestId, + }); +} diff --git a/src/lib/server/host.test.ts b/src/lib/server/host.test.ts new file mode 100644 index 0000000000..dfd0a04a52 --- /dev/null +++ b/src/lib/server/host.test.ts @@ -0,0 +1,297 @@ +import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; +import { getOriginalHost, getOriginalHostWithProtocol } from "./host"; + +// Mock the Next.js headers function +vi.mock("next/headers", () => ({ + headers: vi.fn(), +})); + +describe("Host utility functions", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("getOriginalHost", () => { + test("should return x-forwarded-host when available", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn((key: string) => { + if (key === "x-forwarded-host") return "zitadel.com"; + if (key === "x-original-host") return "backup.com"; + if (key === "host") return "internal.vercel.app"; + return null; + }), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHost(); + expect(result).toBe("zitadel.com"); + expect(mockHeaders.get).toHaveBeenCalledWith("x-forwarded-host"); + }); + + test("should fall back to x-original-host when x-forwarded-host is not available", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn((key: string) => { + if (key === "x-forwarded-host") return null; + if (key === "x-original-host") return "original.com"; + if (key === "host") return "internal.vercel.app"; + return null; + }), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHost(); + expect(result).toBe("original.com"); + expect(mockHeaders.get).toHaveBeenCalledWith("x-forwarded-host"); + expect(mockHeaders.get).toHaveBeenCalledWith("x-original-host"); + }); + + test("should fall back to host when forwarded headers are not available", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn((key: string) => { + if (key === "x-forwarded-host") return null; + if (key === "x-original-host") return null; + if (key === "host") return "fallback.com"; + return null; + }), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHost(); + expect(result).toBe("fallback.com"); + expect(mockHeaders.get).toHaveBeenCalledWith("x-forwarded-host"); + expect(mockHeaders.get).toHaveBeenCalledWith("x-original-host"); + expect(mockHeaders.get).toHaveBeenCalledWith("host"); + }); + + test("should throw error when no host is found", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => null), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + await expect(getOriginalHost()).rejects.toThrow("No host found in headers"); + }); + + test("should throw error when host is empty string", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => ""), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + await expect(getOriginalHost()).rejects.toThrow("No host found in headers"); + }); + + test("should throw error when host is not a string", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => 123), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + await expect(getOriginalHost()).rejects.toThrow("No host found in headers"); + }); + }); + + describe("getOriginalHostWithProtocol", () => { + test("should return https for production domain", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => "zitadel.com"), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("https://zitadel.com"); + }); + + test("should return http for localhost", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => "localhost:3000"), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("http://localhost:3000"); + }); + + test("should return http for localhost without port", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => "localhost"), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("http://localhost"); + }); + + test("should return https for custom domain", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => "auth.company.com"), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("https://auth.company.com"); + }); + }); + + describe("Real-world scenarios", () => { + test("should handle Vercel rewrite scenario", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn((key: string) => { + // Simulate Vercel rewrite: zitadel.com/login -> login-zitadel-qa.vercel.app + if (key === "x-forwarded-host") return "zitadel.com"; + if (key === "host") return "login-zitadel-qa.vercel.app"; + return null; + }), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("https://zitadel.com"); + }); + + test("should handle CloudFlare proxy scenario", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn((key: string) => { + if (key === "x-forwarded-host") return "auth.company.com"; + if (key === "x-original-host") return null; + if (key === "host") return "cloudflare-worker.workers.dev"; + return null; + }), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHost(); + expect(result).toBe("auth.company.com"); + }); + + test("should handle development environment", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn((key: string) => { + if (key === "host") return "localhost:3000"; + return null; + }), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("http://localhost:3000"); + }); + + test("should handle staging environment with subdomain", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn((key: string) => { + if (key === "x-forwarded-host") return "staging-auth.company.com"; + if (key === "host") return "staging-internal.vercel.app"; + return null; + }), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("https://staging-auth.company.com"); + }); + }); + + describe("Edge cases", () => { + test("should handle IPv4 addresses", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => "192.168.1.100:3000"), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("https://192.168.1.100:3000"); + }); + + test("should handle IPv6 addresses", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => "[::1]:3000"), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("https://[::1]:3000"); + }); + + test("should handle hosts with ports", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => "zitadel.com:8080"), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("https://zitadel.com:8080"); + }); + + test("should handle localhost with different ports", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn(() => "localhost:8080"), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHostWithProtocol(); + expect(result).toBe("http://localhost:8080"); + }); + + test("should handle priority order correctly", async () => { + const { headers } = await import("next/headers"); + const mockHeaders = { + get: vi.fn((key: string) => { + // All headers are present, should return x-forwarded-host (highest priority) + if (key === "x-forwarded-host") return "priority1.com"; + if (key === "x-original-host") return "priority2.com"; + if (key === "host") return "priority3.com"; + return null; + }), + }; + + vi.mocked(headers).mockResolvedValue(mockHeaders as any); + + const result = await getOriginalHost(); + expect(result).toBe("priority1.com"); + // Should only call x-forwarded-host since it's available + expect(mockHeaders.get).toHaveBeenCalledWith("x-forwarded-host"); + expect(mockHeaders.get).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/lib/server/host.ts b/src/lib/server/host.ts new file mode 100644 index 0000000000..9d2d57b8a9 --- /dev/null +++ b/src/lib/server/host.ts @@ -0,0 +1,48 @@ +import { headers } from "next/headers"; + +/** + * Gets the original host that the user sees in their browser URL. + * When using rewrites this function prioritizes forwarded headers that preserve the original host. + * + * ⚠️ SERVER-SIDE ONLY: This function can only be used in: + * - Server Actions (functions with "use server") + * - Server Components (React components that run on the server) + * - Route Handlers (API routes) + * - Middleware + * + * @returns The host string (e.g., "zitadel.com") + * @throws Error if no host is found + */ +export async function getOriginalHost(): Promise { + const _headers = await headers(); + + // Priority order: + // 1. x-forwarded-host - Set by proxies/CDNs with the original host + // 2. x-original-host - Alternative header sometimes used + // 3. host - Fallback to the current host header + const host = _headers.get("x-forwarded-host") || _headers.get("x-original-host") || _headers.get("host"); + + if (!host || typeof host !== "string") { + throw new Error("No host found in headers"); + } + + return host; +} + +/** + * Gets the original host with protocol prefix. + * Automatically detects if localhost should use http:// or https:// + * + * ⚠️ SERVER-SIDE ONLY: This function can only be used in: + * - Server Actions (functions with "use server") + * - Server Components (React components that run on the server) + * - Route Handlers (API routes) + * - Middleware + * + * @returns The full URL prefix (e.g., "https://zitadel.com") + */ +export async function getOriginalHostWithProtocol(): Promise { + const host = await getOriginalHost(); + const protocol = host.includes("localhost") ? "http://" : "https://"; + return `${protocol}${host}`; +} diff --git a/src/lib/server/idp.ts b/src/lib/server/idp.ts index 87f88a7c32..96773e6f6e 100644 --- a/src/lib/server/idp.ts +++ b/src/lib/server/idp.ts @@ -3,28 +3,24 @@ import { getLoginSettings, getUserByID, + listAuthenticationMethodTypes, startIdentityProviderFlow, startLDAPIdentityProviderFlow, } from "@/lib/zitadel"; import { headers } from "next/headers"; import { redirect } from "next/navigation"; -import { getNextUrl } from "../client"; +import { completeFlowOrGetUrl } from "../client"; import { getServiceUrlFromHeaders } from "../service-url"; -import { checkEmailVerification } from "../verify-helper"; +import { checkEmailVerification, checkMFAFactors } from "../verify-helper"; import { createSessionForIdpAndUpdateCookie } from "./cookie"; +import { getOriginalHost } from "./host"; export type RedirectToIdpState = { error?: string | null } | undefined; -export async function redirectToIdp( - prevState: RedirectToIdpState, - formData: FormData, -): Promise { +export async function redirectToIdp(prevState: RedirectToIdpState, formData: FormData): Promise { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - if (!host) { - return { error: "Could not get host" }; - } + const host = await getOriginalHost(); const params = new URLSearchParams(); @@ -90,7 +86,7 @@ async function startIDPFlow(command: StartIDPFlowCommand) { return { redirect: url }; } -type CreateNewSessionCommand = { +export type CreateNewSessionCommand = { userId: string; idpIntent: { idpIntentId: string; @@ -102,17 +98,10 @@ type CreateNewSessionCommand = { requestId?: string; }; -export async function createNewSessionFromIdpIntent( - command: CreateNewSessionCommand, -) { +export async function createNewSessionFromIdpIntent(command: CreateNewSessionCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host) { - return { error: "Could not get domain" }; - } if (!command.userId || !command.idpIntent) { throw new Error("No userId or loginName provided"); @@ -143,30 +132,41 @@ export async function createNewSessionFromIdpIntent( return { error: "Could not create session" }; } - const humanUser = - userResponse.user.type.case === "human" - ? userResponse.user.type.value - : undefined; + const humanUser = userResponse.user.type.case === "human" ? userResponse.user.type.value : undefined; // check to see if user was verified - const emailVerificationCheck = checkEmailVerification( + const emailVerificationCheck = checkEmailVerification(session, humanUser, command.organization, command.requestId); + + if (emailVerificationCheck?.redirect) { + return emailVerificationCheck; + } + + // check if user has MFA methods + let authMethods; + if (session.factors?.user?.id) { + const response = await listAuthenticationMethodTypes({ + serviceUrl, + userId: session.factors.user.id, + }); + if (response.authMethodTypes && response.authMethodTypes.length) { + authMethods = response.authMethodTypes; + } + } + + const mfaFactorCheck = await checkMFAFactors( + serviceUrl, session, - humanUser, + loginSettings, + authMethods || [], // Pass empty array if no auth methods command.organization, command.requestId, ); - if (emailVerificationCheck?.redirect) { - return emailVerificationCheck; + if (mfaFactorCheck?.redirect) { + return mfaFactorCheck; } - // TODO: check if user has MFA methods - // const mfaFactorCheck = checkMFAFactors(session, loginSettings, authMethods, organization, requestId); - // if (mfaFactorCheck?.redirect) { - // return mfaFactorCheck; - // } - - const url = await getNextUrl( + return completeFlowOrGetUrl( command.requestId && session.id ? { sessionId: session.id, @@ -179,10 +179,6 @@ export async function createNewSessionFromIdpIntent( }, loginSettings?.defaultRedirectUri, ); - - if (url) { - return { redirect: url }; - } } type createNewSessionForLDAPCommand = { @@ -192,17 +188,10 @@ type createNewSessionForLDAPCommand = { link: boolean; }; -export async function createNewSessionForLDAP( - command: createNewSessionForLDAPCommand, -) { +export async function createNewSessionForLDAP(command: createNewSessionForLDAPCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host) { - return { error: "Could not get domain" }; - } if (!command.username || !command.password) { return { error: "No username or password provided" }; @@ -215,11 +204,7 @@ export async function createNewSessionForLDAP( password: command.password, }); - if ( - !response || - response.nextStep.case !== "idpIntent" || - !response.nextStep.value - ) { + if (!response || response.nextStep.case !== "idpIntent" || !response.nextStep.value) { return { error: "Could not start LDAP identity provider flow" }; } diff --git a/src/lib/server/loginname.test.ts b/src/lib/server/loginname.test.ts new file mode 100644 index 0000000000..a394e48051 --- /dev/null +++ b/src/lib/server/loginname.test.ts @@ -0,0 +1,548 @@ +import { describe, expect, test, vi, beforeEach, afterEach } from "vitest"; +import { sendLoginname } from "./loginname"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; +import { UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; +import { getIDPByID } from "../zitadel"; + +// Mock all the dependencies +vi.mock("next/headers", () => ({ + headers: vi.fn(), +})); + +vi.mock("@zitadel/client", () => ({ + create: vi.fn(), +})); + +vi.mock("../service-url", () => ({ + getServiceUrlFromHeaders: vi.fn(), +})); + +vi.mock("../idp", () => ({ + idpTypeToIdentityProviderType: vi.fn(), + idpTypeToSlug: vi.fn(), +})); + +vi.mock("../zitadel", () => ({ + getActiveIdentityProviders: vi.fn(), + getIDPByID: vi.fn(), + getLoginSettings: vi.fn(), + getOrgsByDomain: vi.fn(), + listAuthenticationMethodTypes: vi.fn(), + listIDPLinks: vi.fn(), + searchUsers: vi.fn(), + startIdentityProviderFlow: vi.fn(), +})); + +vi.mock("./cookie", () => ({ + createSessionAndUpdateCookie: vi.fn(), +})); + +vi.mock("./host", () => ({ + getOriginalHost: vi.fn(), +})); + +describe("sendLoginname", () => { + // Mock modules + let mockHeaders: any; + let mockCreate: any; + let mockGetServiceUrlFromHeaders: any; + let mockGetLoginSettings: any; + let mockSearchUsers: any; + let mockCreateSessionAndUpdateCookie: any; + let mockListAuthenticationMethodTypes: any; + let mockListIDPLinks: any; + let mockGetOriginalHost: any; + let mockStartIdentityProviderFlow: any; + let mockGetActiveIdentityProviders: any; + let mockGetIDPByID: any; + let mockIdpTypeToSlug: any; + + beforeEach(async () => { + vi.clearAllMocks(); + + // Import mocked modules + const { headers } = await import("next/headers"); + const { create } = await import("@zitadel/client"); + const { getServiceUrlFromHeaders } = await import("../service-url"); + const { + getLoginSettings, + searchUsers, + listAuthenticationMethodTypes, + listIDPLinks, + startIdentityProviderFlow, + getActiveIdentityProviders, + } = await import("../zitadel"); + const { createSessionAndUpdateCookie } = await import("./cookie"); + const { getOriginalHost } = await import("./host"); + const { idpTypeToSlug } = await import("../idp"); + + // Setup mocks + mockHeaders = vi.mocked(headers); + mockCreate = vi.mocked(create); + mockGetServiceUrlFromHeaders = vi.mocked(getServiceUrlFromHeaders); + mockGetLoginSettings = vi.mocked(getLoginSettings); + mockSearchUsers = vi.mocked(searchUsers); + mockCreateSessionAndUpdateCookie = vi.mocked(createSessionAndUpdateCookie); + mockListAuthenticationMethodTypes = vi.mocked(listAuthenticationMethodTypes); + mockListIDPLinks = vi.mocked(listIDPLinks); + mockGetOriginalHost = vi.mocked(getOriginalHost); + mockStartIdentityProviderFlow = vi.mocked(startIdentityProviderFlow); + mockGetActiveIdentityProviders = vi.mocked(getActiveIdentityProviders); + mockGetIDPByID = vi.mocked(getIDPByID); + mockIdpTypeToSlug = vi.mocked(idpTypeToSlug); + + // Default mock implementations + mockHeaders.mockResolvedValue({} as any); + mockGetServiceUrlFromHeaders.mockReturnValue({ serviceUrl: "https://api.example.com" }); + mockGetOriginalHost.mockResolvedValue("example.com"); + mockIdpTypeToSlug.mockReturnValue("google"); + mockGetIDPByID.mockResolvedValue({ + id: "idp123", + name: "Google", + type: "GOOGLE", + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("Error cases", () => { + test("should return error when login settings cannot be retrieved", async () => { + mockGetLoginSettings.mockResolvedValue(null); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ error: "Could not get login settings" }); + }); + + test("should return error when user search fails", async () => { + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockSearchUsers.mockResolvedValue({ error: "Search failed" }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ error: "Search failed" }); + }); + + test("should return error when search result has no result field", async () => { + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockSearchUsers.mockResolvedValue({}); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ error: "Could not search users" }); + }); + + test("should return error when more than one user found", async () => { + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockSearchUsers.mockResolvedValue({ + result: [ + { userId: "user1", preferredLoginName: "user1@example.com" }, + { userId: "user2", preferredLoginName: "user2@example.com" }, + ], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ error: "More than one user found. Provide a unique identifier." }); + }); + }); + + describe("Single user found - authentication method handling", () => { + const mockUser = { + userId: "user123", + preferredLoginName: "user@example.com", + details: { resourceOwner: "org123" }, + type: { case: "human", value: { email: { email: "user@example.com" } } }, + state: UserState.ACTIVE, + }; + + const mockSession = { + factors: { + user: { + id: "user123", + loginName: "user@example.com", + organizationId: "org123", + }, + }, + }; + + beforeEach(() => { + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockSearchUsers.mockResolvedValue({ result: [mockUser] }); + mockCreate.mockReturnValue({}); + mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession); + }); + + test("should redirect to verify when user has no authentication methods", async () => { + mockListAuthenticationMethodTypes.mockResolvedValue({ authMethodTypes: [] }); + + const result = await sendLoginname({ + loginName: "user@example.com", + requestId: "req123", + }); + + expect(result).toHaveProperty("redirect"); + expect((result as any).redirect).toMatch(/^\/verify\?/); + expect((result as any).redirect).toContain("loginName=user%40example.com"); + expect((result as any).redirect).toContain("send=true"); + expect((result as any).redirect).toContain("invite=true"); + expect((result as any).redirect).toContain("requestId=req123"); + }); + + describe("Single authentication method", () => { + test("should redirect to password when user has only password method and it's allowed", async () => { + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + requestId: "req123", + }); + + expect(result).toHaveProperty("redirect"); + expect((result as any).redirect).toMatch(/^\/password\?/); + expect((result as any).redirect).toContain("loginName=user%40example.com"); + expect((result as any).redirect).toContain("requestId=req123"); + }); + + test("should attempt IDP redirect when password is not allowed but user has IDP links", async () => { + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD], + }); + mockListIDPLinks.mockResolvedValue({ + result: [{ idpId: "idp123" }], + }); + mockStartIdentityProviderFlow.mockResolvedValue("https://idp.example.com/auth"); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ redirect: "https://idp.example.com/auth" }); + expect(mockListIDPLinks).toHaveBeenCalledWith({ + serviceUrl: "https://api.example.com", + userId: "user123", + }); + }); + + test("should return error when password not allowed and no IDP links available", async () => { + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD], + }); + mockListIDPLinks.mockResolvedValue({ result: [] }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ + error: "Username Password not allowed! Contact your administrator for more information.", + }); + }); + + test("should redirect to passkey when user has only passkey method and it's allowed", async () => { + mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.ALLOWED }); + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSKEY], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + requestId: "req123", + }); + + expect(result).toHaveProperty("redirect"); + expect((result as any).redirect).toMatch(/^\/passkey\?/); + expect((result as any).redirect).toContain("loginName=user%40example.com"); + expect((result as any).redirect).toContain("requestId=req123"); + }); + + test("should return error when passkeys are not allowed", async () => { + mockGetLoginSettings.mockResolvedValue({ passkeysType: PasskeysType.NOT_ALLOWED }); + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSKEY], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ + error: "Passkeys not allowed! Contact your administrator for more information.", + }); + }); + + test("should redirect to IDP when user has only IDP method", async () => { + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.IDP], + }); + mockListIDPLinks.mockResolvedValue({ + result: [{ idpId: "idp123" }], + }); + mockStartIdentityProviderFlow.mockResolvedValue("https://idp.example.com/auth"); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ redirect: "https://idp.example.com/auth" }); + }); + }); + + describe("Multiple authentication methods", () => { + test("should prefer passkey when multiple methods available", async () => { + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.PASSKEY], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toHaveProperty("redirect"); + expect((result as any).redirect).toMatch(/^\/passkey\?/); + expect((result as any).redirect).toContain("altPassword=true"); // password is allowed + }); + + test("should not show password alternative when password is not allowed", async () => { + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.PASSKEY], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toBeDefined(); + expect(result?.redirect).toMatch(/^\/passkey\?/); + expect(result?.redirect).toContain("altPassword=false"); // password is not allowed + }); + + test("should redirect to IDP when no passkey but IDP available", async () => { + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.IDP], + }); + mockListIDPLinks.mockResolvedValue({ + result: [{ idpId: "idp123" }], + }); + mockStartIdentityProviderFlow.mockResolvedValue("https://idp.example.com/auth"); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ redirect: "https://idp.example.com/auth" }); + }); + + test("should redirect to password when no passkey or IDP, only password available and allowed", async () => { + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toBeDefined(); + expect(result?.redirect).toMatch(/^\/password\?/); + }); + + test("should return error when password is only method in multi-method scenario but not allowed", async () => { + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: false }); + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD], + }); + mockListIDPLinks.mockResolvedValue({ result: [] }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ + error: "Username Password not allowed! Contact your administrator for more information.", + }); + }); + }); + }); + + describe("User not found scenarios", () => { + beforeEach(() => { + mockSearchUsers.mockResolvedValue({ result: [] }); + }); + + test("should redirect to single IDP when register allowed but password not allowed", async () => { + mockGetLoginSettings.mockResolvedValue({ + allowRegister: true, + allowUsernamePassword: false, + }); + mockGetActiveIdentityProviders.mockResolvedValue({ + identityProviders: [{ id: "idp123", type: "OIDC" }], + }); + mockStartIdentityProviderFlow.mockResolvedValue("https://idp.example.com/auth"); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ redirect: "https://idp.example.com/auth" }); + }); + + test("should redirect to register when both register and password allowed", async () => { + mockGetLoginSettings.mockResolvedValue({ + allowRegister: true, + allowUsernamePassword: true, + ignoreUnknownUsernames: false, + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + organization: "org123", + requestId: "req123", + }); + + expect(result).toBeDefined(); + expect(result?.redirect).toMatch(/^\/register\?/); + expect(result?.redirect).toContain("organization=org123"); + expect(result?.redirect).toContain("requestId=req123"); + expect(result?.redirect).toContain("email=user%40example.com"); + }); + + test("should redirect to password when ignoreUnknownUsernames is true", async () => { + mockGetLoginSettings.mockResolvedValue({ + ignoreUnknownUsernames: true, + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + requestId: "req123", + organization: "org123", + }); + + expect(result).toBeDefined(); + expect(result?.redirect).toMatch(/^\/password\?/); + expect(result?.redirect).toContain("loginName=user%40example.com"); + expect(result?.redirect).toContain("requestId=req123"); + expect(result?.redirect).toContain("organization=org123"); + }); + + test("should return error when user not found and no registration allowed", async () => { + mockGetLoginSettings.mockResolvedValue({ + allowRegister: false, + allowUsernamePassword: true, + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ error: "User not found in the system" }); + }); + }); + + describe("Edge cases", () => { + test("should handle session creation failure", async () => { + const mockUser = { + userId: "user123", + preferredLoginName: "user@example.com", + details: { resourceOwner: "org123" }, + type: { case: "human", value: { email: { email: "user@example.com" } } }, + state: UserState.ACTIVE, + }; + + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockSearchUsers.mockResolvedValue({ result: [mockUser] }); + mockCreate.mockReturnValue({}); + mockCreateSessionAndUpdateCookie.mockResolvedValue({ factors: {} }); // No user in session + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ error: "Could not create session for user" }); + }); + + test("should handle initial user state", async () => { + const mockUser = { + userId: "user123", + preferredLoginName: "user@example.com", + details: { resourceOwner: "org123" }, + type: { case: "human", value: { email: { email: "user@example.com" } } }, + state: UserState.INITIAL, + }; + + const mockSession = { + factors: { + user: { + id: "user123", + loginName: "user@example.com", + organizationId: "org123", + }, + }, + }; + + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockSearchUsers.mockResolvedValue({ result: [mockUser] }); + mockCreate.mockReturnValue({}); + mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession); + + const result = await sendLoginname({ + loginName: "user@example.com", + }); + + expect(result).toEqual({ error: "Initial User not supported" }); + }); + + test("should handle organization parameter in all redirects", async () => { + const mockUser = { + userId: "user123", + preferredLoginName: "user@example.com", + details: { resourceOwner: "org123" }, + type: { case: "human", value: { email: { email: "user@example.com" } } }, + state: UserState.ACTIVE, + }; + + const mockSession = { + factors: { + user: { + id: "user123", + loginName: "user@example.com", + organizationId: "org123", + }, + }, + }; + + mockGetLoginSettings.mockResolvedValue({ allowUsernamePassword: true }); + mockSearchUsers.mockResolvedValue({ result: [mockUser] }); + mockCreate.mockReturnValue({}); + mockCreateSessionAndUpdateCookie.mockResolvedValue(mockSession); + mockListAuthenticationMethodTypes.mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD], + }); + + const result = await sendLoginname({ + loginName: "user@example.com", + organization: "custom-org", + requestId: "req123", + }); + + expect(result).toBeDefined(); + expect(result?.redirect).toContain("organization=custom-org"); + expect(result?.redirect).toContain("requestId=req123"); + }); + }); +}); diff --git a/src/lib/server/loginname.ts b/src/lib/server/loginname.ts index dee740bf4f..9f243a86d4 100644 --- a/src/lib/server/loginname.ts +++ b/src/lib/server/loginname.ts @@ -21,6 +21,7 @@ import { startIdentityProviderFlow, } from "../zitadel"; import { createSessionAndUpdateCookie } from "./cookie"; +import { getOriginalHost } from "./host"; export type SendLoginnameCommand = { loginName: string; @@ -34,11 +35,6 @@ const ORG_SUFFIX_REGEX = /(?<=@)(.+)/; export async function sendLoginname(command: SendLoginnameCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host) { - throw new Error("Could not get domain"); - } const loginSettingsByContext = await getLoginSettings({ serviceUrl, @@ -80,11 +76,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host) { - return { error: "Could not get host" }; - } + const host = await getOriginalHost(); const identityProviderType = identityProviders[0].type; @@ -134,11 +126,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { if (identityProviders.length === 1) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host) { - return { error: "Could not get host" }; - } + const host = await getOriginalHost(); const identityProviderId = identityProviders[0].idpId; @@ -201,35 +189,21 @@ export async function sendLoginname(command: SendLoginnameCommand) { }); // compare with the concatenated suffix when set - const concatLoginname = command.suffix - ? `${command.loginName}@${command.suffix}` - : command.loginName; + const concatLoginname = command.suffix ? `${command.loginName}@${command.suffix}` : command.loginName; - const humanUser = - potentialUsers[0].type.case === "human" - ? potentialUsers[0].type.value - : undefined; + const humanUser = potentialUsers[0].type.case === "human" ? potentialUsers[0].type.value : undefined; // recheck login settings after user discovery, as the search might have been done without org scope - if ( - userLoginSettings?.disableLoginWithEmail && - userLoginSettings?.disableLoginWithPhone - ) { + if (userLoginSettings?.disableLoginWithEmail && userLoginSettings?.disableLoginWithPhone) { if (user.preferredLoginName !== concatLoginname) { return { error: "User not found in the system!" }; } } else if (userLoginSettings?.disableLoginWithEmail) { - if ( - user.preferredLoginName !== concatLoginname || - humanUser?.phone?.phone !== command.loginName - ) { + if (user.preferredLoginName !== concatLoginname || humanUser?.phone?.phone !== command.loginName) { return { error: "User not found in the system!" }; } } else if (userLoginSettings?.disableLoginWithPhone) { - if ( - user.preferredLoginName !== concatLoginname || - humanUser?.email?.email !== command.loginName - ) { + if (user.preferredLoginName !== concatLoginname || humanUser?.email?.email !== command.loginName) { return { error: "User not found in the system!" }; } } @@ -270,11 +244,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } if (command.organization || session.factors?.user?.organizationId) { - params.append( - "organization", - command.organization ?? - (session.factors?.user?.organizationId as string), - ); + params.append("organization", command.organization ?? (session.factors?.user?.organizationId as string)); } return { redirect: `/verify?` + params }; @@ -285,9 +255,14 @@ export async function sendLoginname(command: SendLoginnameCommand) { switch (method) { case AuthenticationMethodType.PASSWORD: // user has only password as auth method if (!userLoginSettings?.allowUsernamePassword) { + // Check if user has IDPs available as alternative, that could eventually be used to register/link. + const idpResp = await redirectUserToIDP(userId); + if (idpResp?.redirect) { + return idpResp; + } + return { - error: - "Username Password not allowed! Contact your administrator for more information.", + error: "Username Password not allowed! Contact your administrator for more information.", }; } @@ -298,10 +273,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { // TODO: does this have to be checked in loginSettings.allowDomainDiscovery if (command.organization || session.factors?.user?.organizationId) { - paramsPassword.append( - "organization", - command.organization ?? session.factors?.user?.organizationId, - ); + paramsPassword.append("organization", command.organization ?? session.factors?.user?.organizationId); } if (command.requestId) { @@ -315,8 +287,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { case AuthenticationMethodType.PASSKEY: // AuthenticationMethodType.AUTHENTICATION_METHOD_TYPE_PASSKEY if (userLoginSettings?.passkeysType === PasskeysType.NOT_ALLOWED) { return { - error: - "Passkeys not allowed! Contact your administrator for more information.", + error: "Passkeys not allowed! Contact your administrator for more information.", }; } @@ -328,20 +299,26 @@ export async function sendLoginname(command: SendLoginnameCommand) { } if (command.organization || session.factors?.user?.organizationId) { - paramsPasskey.append( - "organization", - command.organization ?? session.factors?.user?.organizationId, - ); + paramsPasskey.append("organization", command.organization ?? session.factors?.user?.organizationId); } return { redirect: "/passkey?" + paramsPasskey }; + + case AuthenticationMethodType.IDP: + const resp = await redirectUserToIDP(userId); + + if (resp?.error) { + return { error: resp.error }; + } + + return resp; } } else { // prefer passkey in favor of other methods if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSKEY)) { const passkeyParams = new URLSearchParams({ loginName: session.factors?.user?.loginName, - altPassword: `${methods.authMethodTypes.includes(1)}`, // show alternative password option + altPassword: `${methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) && userLoginSettings?.allowUsernamePassword}`, // show alternative password option only if allowed }); if (command.requestId) { @@ -349,21 +326,21 @@ export async function sendLoginname(command: SendLoginnameCommand) { } if (command.organization || session.factors?.user?.organizationId) { - passkeyParams.append( - "organization", - command.organization ?? session.factors?.user?.organizationId, - ); + passkeyParams.append("organization", command.organization ?? session.factors?.user?.organizationId); } return { redirect: "/passkey?" + passkeyParams }; - } else if ( - methods.authMethodTypes.includes(AuthenticationMethodType.IDP) - ) { + } else if (methods.authMethodTypes.includes(AuthenticationMethodType.IDP)) { return redirectUserToIDP(userId); - } else if ( - methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD) - ) { - // user has no passkey setup and login settings allow passkeys + } else if (methods.authMethodTypes.includes(AuthenticationMethodType.PASSWORD)) { + // Check if password authentication is allowed + if (!userLoginSettings?.allowUsernamePassword) { + return { + error: "Username Password not allowed! Contact your administrator for more information.", + }; + } + + // user has no passkey setup and login settings allow passwords const paramsPasswordDefault = new URLSearchParams({ loginName: session.factors?.user?.loginName, }); @@ -373,10 +350,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { } if (command.organization || session.factors?.user?.organizationId) { - paramsPasswordDefault.append( - "organization", - command.organization ?? session.factors?.user?.organizationId, - ); + paramsPasswordDefault.append("organization", command.organization ?? session.factors?.user?.organizationId); } return { @@ -387,19 +361,13 @@ export async function sendLoginname(command: SendLoginnameCommand) { } // user not found, check if register is enabled on instance / organization context - if ( - loginSettingsByContext?.allowRegister && - !loginSettingsByContext?.allowUsernamePassword - ) { + if (loginSettingsByContext?.allowRegister && !loginSettingsByContext?.allowUsernamePassword) { const resp = await redirectUserToSingleIDPIfAvailable(); if (resp) { return resp; } return { error: "User not found in the system" }; - } else if ( - loginSettingsByContext?.allowRegister && - loginSettingsByContext?.allowUsernamePassword - ) { + } else if (loginSettingsByContext?.allowRegister && loginSettingsByContext?.allowUsernamePassword) { let orgToRegisterOn: string | undefined = command.organization; if ( @@ -416,8 +384,7 @@ export async function sendLoginname(command: SendLoginnameCommand) { serviceUrl, domain: suffix, }); - const orgToCheckForDiscovery = - orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; + const orgToCheckForDiscovery = orgs.result && orgs.result.length === 1 ? orgs.result[0].id : undefined; const orgLoginSettings = await getLoginSettings({ serviceUrl, diff --git a/src/lib/server/passkeys.ts b/src/lib/server/passkeys.ts index ca603471ab..83a354a277 100644 --- a/src/lib/server/passkeys.ts +++ b/src/lib/server/passkeys.ts @@ -11,35 +11,33 @@ import { } from "@/lib/zitadel"; import { create, Duration, Timestamp, timestampDate } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { Checks, ChecksSchema, GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { RegisterPasskeyResponse, VerifyPasskeyRegistrationRequestSchema, } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; import { userAgent } from "next/server"; -import { getNextUrl } from "../client"; -import { - getMostRecentSessionCookie, - getSessionCookieById, - getSessionCookieByLoginName, -} from "../cookies"; +import { getMostRecentSessionCookie, getSessionCookieById, getSessionCookieByLoginName } from "../cookies"; import { getServiceUrlFromHeaders } from "../service-url"; -import { - checkEmailVerification, - checkUserVerification, -} from "../verify-helper"; -import { setSessionAndUpdateCookie } from "./cookie"; +import { checkEmailVerification, checkUserVerification } from "../verify-helper"; +import { createSessionAndUpdateCookie, setSessionAndUpdateCookie } from "./cookie"; +import { getOriginalHost } from "./host"; +import { completeFlowOrGetUrl } from "../client"; type VerifyPasskeyCommand = { passkeyId: string; passkeyName?: string; publicKeyCredential: any; - sessionId: string; + sessionId?: string; + userId?: string; }; type RegisterPasskeyCommand = { - sessionId: string; + sessionId?: string; + userId?: string; + code?: string; + codeId?: string; }; function isSessionValid(session: Partial): { @@ -48,9 +46,7 @@ function isSessionValid(session: Partial): { } { const validPassword = session?.factors?.password?.verifiedAt; const validPasskey = session?.factors?.webAuthN?.verifiedAt; - const stillValid = session.expirationDate - ? timestampDate(session.expirationDate) > new Date() - : true; + const stillValid = session.expirationDate ? timestampDate(session.expirationDate) > new Date() : true; const verifiedAt = validPassword || validPasskey; const valid = !!((validPassword || validPasskey) && stillValid); @@ -61,80 +57,126 @@ function isSessionValid(session: Partial): { export async function registerPasskeyLink( command: RegisterPasskeyCommand, ): Promise { - const { sessionId } = command; + if (!command.sessionId && !command.userId) { + return { error: "Either sessionId or userId must be provided" }; + } const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); + const host = await getOriginalHost(); - if (!host) { - throw new Error("Could not get domain"); - } + let session: GetSessionResponse | undefined; + let createdSession: Session | undefined; + let currentUserId: string | undefined = undefined; + let registerCode: { id: string; code: string } | undefined = undefined; - const sessionCookie = await getSessionCookieById({ sessionId }); - const session = await getSession({ - serviceUrl, - sessionId: sessionCookie.id, - sessionToken: sessionCookie.token, - }); + if (command.sessionId) { + // Session-based flow (existing logic) + const sessionCookie = await getSessionCookieById({ sessionId: command.sessionId }); + session = await getSession({ + serviceUrl, + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); - if (!session?.session?.factors?.user?.id) { - return { error: "Could not determine user from session" }; - } + if (!session?.session?.factors?.user?.id) { + return { error: "Could not determine user from session" }; + } + + currentUserId = session.session.factors.user.id; + + const sessionValid = isSessionValid(session.session); + + if (!sessionValid.valid) { + const authmethods = await listAuthenticationMethodTypes({ + serviceUrl, + userId: currentUserId, + }); - const sessionValid = isSessionValid(session.session); + // if the user has no authmethods set, we need to check if the user was verified + if (authmethods.authMethodTypes.length !== 0) { + return { + error: "You have to authenticate or have a valid User Verification Check", + }; + } - if (!sessionValid) { - const authmethods = await listAuthenticationMethodTypes({ + // check if a verification was done earlier + const hasValidUserVerificationCheck = await checkUserVerification(currentUserId); + + console.log("hasValidUserVerificationCheck", hasValidUserVerificationCheck); + if (!hasValidUserVerificationCheck) { + return { error: "User Verification Check has to be done" }; + } + + if (!command.code) { + // request a new code if no code is provided + const codeResponse = await createPasskeyRegistrationLink({ + serviceUrl, + userId: currentUserId, + }); + + if (!codeResponse?.code?.code) { + return { error: "Could not create registration link" }; + } + + registerCode = codeResponse.code; + } + } + } else if (command.userId && command.code && command.codeId) { + currentUserId = command.userId; + registerCode = { + id: command.codeId, + code: command.code, + }; + + // Check if user exists + const userResponse = await getUserByID({ serviceUrl, - userId: session.session.factors.user.id, + userId: currentUserId, }); - // if the user has no authmethods set, we need to check if the user was verified - if (authmethods.authMethodTypes.length !== 0) { - return { - error: - "You have to authenticate or have a valid User Verification Check", - }; + if (!userResponse || !userResponse.user) { + return { error: "User not found" }; } - // check if a verification was done earlier - const hasValidUserVerificationCheck = await checkUserVerification( - session.session.factors.user.id, - ); + // Create a session for the user to continue the flow after passkey registration + const checks = create(ChecksSchema, { + user: { + search: { + case: "loginName", + value: userResponse.user.preferredLoginName, + }, + }, + }); + + createdSession = await createSessionAndUpdateCookie({ + checks, + requestId: undefined, // No requestId in passkey registration context, TODO: consider if needed + }); - if (!hasValidUserVerificationCheck) { - return { error: "User Verification Check has to be done" }; + if (!createdSession) { + return { error: "Could not create session" }; } } + if (!registerCode) { + throw new Error("Missing code in response"); + } + const [hostname] = host.split(":"); if (!hostname) { throw new Error("Could not get hostname"); } - const userId = session?.session?.factors?.user?.id; - - if (!userId) { - throw new Error("Could not get session"); - } - // TODO: add org context - - // use session token to add the passkey - const registerLink = await createPasskeyRegistrationLink({ - serviceUrl, - userId, - }); - - if (!registerLink.code) { - throw new Error("Missing code in response"); + if (!currentUserId) { + throw new Error("Could not determine user"); } return registerPasskey({ serviceUrl, - userId, - code: registerLink.code, + userId: currentUserId, + code: registerCode, domain: hostname, }); } @@ -143,6 +185,10 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); + if (!command.sessionId && !command.userId) { + throw new Error("Either sessionId or userId must be provided"); + } + // if no name is provided, try to generate one from the user agent let passkeyName = command.passkeyName; if (!passkeyName) { @@ -155,18 +201,38 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { }${os.name}${os.name ? ", " : ""}${browser.name}`; } - const sessionCookie = await getSessionCookieById({ - sessionId: command.sessionId, - }); - const session = await getSession({ - serviceUrl, - sessionId: sessionCookie.id, - sessionToken: sessionCookie.token, - }); - const userId = session?.session?.factors?.user?.id; + let currentUserId: string; - if (!userId) { - throw new Error("Could not get session"); + if (command.sessionId) { + // Session-based flow + const sessionCookie = await getSessionCookieById({ + sessionId: command.sessionId, + }); + const session = await getSession({ + serviceUrl, + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); + const userId = session?.session?.factors?.user?.id; + + if (!userId) { + throw new Error("Could not get session"); + } + + currentUserId = userId; + } else { + // UserId-based flow + currentUserId = command.userId!; + + // Verify user exists + const userResponse = await getUserByID({ + serviceUrl, + userId: currentUserId, + }); + + if (!userResponse || !userResponse.user) { + throw new Error("User not found"); + } } return zitadelVerifyPasskeyRegistration({ @@ -175,7 +241,7 @@ export async function verifyPasskeyRegistration(command: VerifyPasskeyCommand) { passkeyId: command.passkeyId, publicKeyCredential: command.publicKeyCredential, passkeyName, - userId, + userId: currentUserId, }), }); } @@ -246,41 +312,30 @@ export async function sendPasskey(command: SendPasskeyCommand) { return { error: "User not found in the system" }; } - const humanUser = - userResponse.user.type.case === "human" - ? userResponse.user.type.value - : undefined; + const humanUser = userResponse.user.type.case === "human" ? userResponse.user.type.value : undefined; - const emailVerificationCheck = checkEmailVerification( - session, - humanUser, - organization, - requestId, - ); + const emailVerificationCheck = checkEmailVerification(session, humanUser, organization, requestId); if (emailVerificationCheck?.redirect) { return emailVerificationCheck; } - const url = - requestId && session.id - ? await getNextUrl( - { - sessionId: session.id, - requestId: requestId, - organization: organization, - }, - loginSettings?.defaultRedirectUri, - ) - : session?.factors?.user?.loginName - ? await getNextUrl( - { - loginName: session.factors.user.loginName, - organization: organization, - }, - loginSettings?.defaultRedirectUri, - ) - : null; - - return { redirect: url }; + if (requestId && session.id) { + return completeFlowOrGetUrl( + { + sessionId: session.id, + requestId: requestId, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ); + } else if (session?.factors?.user?.loginName) { + return completeFlowOrGetUrl( + { + loginName: session.factors.user.loginName, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ); + } } diff --git a/src/lib/server/password.ts b/src/lib/server/password.ts index 013255d06f..166a11d5f1 100644 --- a/src/lib/server/password.ts +++ b/src/lib/server/password.ts @@ -1,9 +1,6 @@ "use server"; -import { - createSessionAndUpdateCookie, - setSessionAndUpdateCookie, -} from "@/lib/server/cookie"; +import { createSessionAndUpdateCookie, setSessionAndUpdateCookie } from "@/lib/server/cookie"; import { getLockoutSettings, getLoginSettings, @@ -18,20 +15,15 @@ import { } from "@/lib/zitadel"; import { ConnectError, create, Duration } from "@zitadel/client"; import { createUserServiceClient } from "@zitadel/client/v2"; -import { - Checks, - ChecksSchema, -} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { Checks, ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { User, UserState } from "@zitadel/proto/zitadel/user/v2/user_pb"; -import { - AuthenticationMethodType, - SetPasswordRequestSchema, -} from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { SetPasswordRequestSchema } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { headers } from "next/headers"; -import { getNextUrl } from "../client"; +import { completeFlowOrGetUrl } from "../client"; import { getSessionCookieById, getSessionCookieByLoginName } from "../cookies"; import { getServiceUrlFromHeaders } from "../service-url"; +import { getOriginalHostWithProtocol } from "./host"; import { checkEmailVerification, checkMFAFactors, @@ -49,11 +41,9 @@ type ResetPasswordCommand = { export async function resetPassword(command: ResetPasswordCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + // Get the original host that the user sees with protocol + const hostWithProtocol = await getOriginalHostWithProtocol(); const users = await listUsers({ serviceUrl, @@ -61,11 +51,7 @@ export async function resetPassword(command: ResetPasswordCommand) { organizationId: command.organization, }); - if ( - !users.details || - users.details.totalResult !== BigInt(1) || - !users.result[0].userId - ) { + if (!users.details || users.details.totalResult !== BigInt(1) || !users.result[0].userId) { return { error: "Could not send Password Reset Link" }; } const userId = users.result[0].userId; @@ -76,7 +62,7 @@ export async function resetPassword(command: ResetPasswordCommand) { serviceUrl, userId, urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + + `${hostWithProtocol}${basePath}/password/set?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + (command.requestId ? `&requestId=${command.requestId}` : ""), }); } @@ -88,7 +74,7 @@ export type UpdateSessionCommand = { requestId?: string; }; -export async function sendPassword(command: UpdateSessionCommand) { +export async function sendPassword(command: UpdateSessionCommand): Promise<{ error: string } | { redirect: string }> { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -139,18 +125,17 @@ export async function sendPassword(command: UpdateSessionCommand) { return { error: `Failed to authenticate. You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.` + - (lockoutSettings?.maxPasswordAttempts && - error.failedAttempts >= lockoutSettings?.maxPasswordAttempts + (lockoutSettings?.maxPasswordAttempts && error.failedAttempts >= lockoutSettings?.maxPasswordAttempts ? "Contact your administrator to unlock your account" : ""), }; } return { error: "Could not create session for user" }; } + } else { + // this is a fake error message to hide that the user does not even exist + return { error: "Could not verify password" }; } - - // this is a fake error message to hide that the user does not even exist - return { error: "Could not verify password" }; } else { loginSettings = await getLoginSettings({ serviceUrl, @@ -188,8 +173,7 @@ export async function sendPassword(command: UpdateSessionCommand) { return { error: `Failed to authenticate. You had ${error.failedAttempts} of ${lockoutSettings?.maxPasswordAttempts} password attempts.` + - (lockoutSettings?.maxPasswordAttempts && - error.failedAttempts >= lockoutSettings?.maxPasswordAttempts + (lockoutSettings?.maxPasswordAttempts && error.failedAttempts >= lockoutSettings?.maxPasswordAttempts ? " Contact your administrator to unlock your account" : ""), }; @@ -216,12 +200,11 @@ export async function sendPassword(command: UpdateSessionCommand) { if (!loginSettings) { loginSettings = await getLoginSettings({ serviceUrl, - organization: - command.organization ?? session.factors?.user?.organizationId, + organization: command.organization ?? session.factors?.user?.organizationId, }); } - if (!session?.factors?.user?.id || !sessionCookie) { + if (!session?.factors?.user?.id) { return { error: "Could not create session for user" }; } @@ -251,12 +234,7 @@ export async function sendPassword(command: UpdateSessionCommand) { } // check to see if user was verified - const emailVerificationCheck = checkEmailVerification( - session, - humanUser, - command.organization, - command.requestId, - ); + const emailVerificationCheck = checkEmailVerification(session, humanUser, command.organization, command.requestId); if (emailVerificationCheck?.redirect) { return emailVerificationCheck; @@ -292,36 +270,49 @@ export async function sendPassword(command: UpdateSessionCommand) { } if (command.requestId && session.id) { - const nextUrl = await getNextUrl( + // OIDC/SAML flow - use completeFlowOrGetUrl for proper handling + console.log("Password auth: OIDC/SAML flow with requestId:", command.requestId, "sessionId:", session.id); + const result = await completeFlowOrGetUrl( { sessionId: session.id, requestId: command.requestId, - organization: - command.organization ?? session.factors?.user?.organizationId, + organization: command.organization ?? session.factors?.user?.organizationId, }, loginSettings?.defaultRedirectUri, ); + console.log("Password auth: OIDC/SAML flow result:", result); + + // Safety net - ensure we always return a valid object + if (!result || typeof result !== "object" || (!("redirect" in result) && !("error" in result))) { + console.error("Password auth: Invalid result from completeFlowOrGetUrl (OIDC/SAML):", result); + return { error: "Authentication completed but navigation failed" }; + } - return { redirect: nextUrl }; + return result; } - const url = await getNextUrl( + // Regular flow (no requestId) - return URL for client-side navigation + console.log("Password auth: Regular flow with loginName:", session.factors.user.loginName); + const result = await completeFlowOrGetUrl( { loginName: session.factors.user.loginName, organization: session.factors?.user?.organizationId, }, loginSettings?.defaultRedirectUri, ); + console.log("Password auth: Regular flow result:", result); + + // Safety net - ensure we always return a valid object + if (!result || typeof result !== "object" || (!("redirect" in result) && !("error" in result))) { + console.error("Password auth: Invalid result from completeFlowOrGetUrl:", result); + return { error: "Authentication completed but navigation failed" }; + } - return { redirect: url }; + return result; } // this function lets users with code set a password or users with valid User Verification Check -export async function changePassword(command: { - code?: string; - userId: string; - password: string; -}) { +export async function changePassword(command: { code?: string; userId: string; password: string }) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -350,15 +341,12 @@ export async function changePassword(command: { // if the user has no authmethods set, we need to check if the user was verified if (authmethods.authMethodTypes.length !== 0) { return { - error: - "You have to provide a code or have a valid User Verification Check", + error: "You have to provide a code or have a valid User Verification Check", }; } // check if a verification was done earlier - const hasValidUserVerificationCheck = await checkUserVerification( - user.userId, - ); + const hasValidUserVerificationCheck = await checkUserVerification(user.userId); if (!hasValidUserVerificationCheck) { return { error: "User Verification Check has to be done" }; @@ -378,20 +366,30 @@ type CheckSessionAndSetPasswordCommand = { password: string; }; -export async function checkSessionAndSetPassword({ - sessionId, - password, -}: CheckSessionAndSetPasswordCommand) { +export async function checkSessionAndSetPassword({ sessionId, password }: CheckSessionAndSetPasswordCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const sessionCookie = await getSessionCookieById({ sessionId }); + let sessionCookie; + try { + sessionCookie = await getSessionCookieById({ sessionId }); + } catch (error) { + console.error("Error getting session cookie:", error); + return { error: "Could not load session cookie" }; + } - const { session } = await getSession({ - serviceUrl, - sessionId: sessionCookie.id, - sessionToken: sessionCookie.token, - }); + let session; + try { + const sessionResponse = await getSession({ + serviceUrl, + sessionId: sessionCookie.id, + sessionToken: sessionCookie.token, + }); + session = sessionResponse.session; + } catch (error) { + console.error("Error getting session:", error); + return { error: "Could not load session" }; + } if (!session || !session.factors?.user?.id) { return { error: "Could not load session" }; @@ -405,44 +403,43 @@ export async function checkSessionAndSetPassword({ }); // check if the user has no password set in order to set a password - const authmethods = await listAuthenticationMethodTypes({ - serviceUrl, - userId: session.factors.user.id, - }); + let authmethods; + try { + authmethods = await listAuthenticationMethodTypes({ + serviceUrl, + userId: session.factors.user.id, + }); + } catch (error) { + console.error("Error getting auth methods:", error); + return { error: "Could not load auth methods" }; + } if (!authmethods) { return { error: "Could not load auth methods" }; } - const requiredAuthMethodsForForceMFA = [ - AuthenticationMethodType.OTP_EMAIL, - AuthenticationMethodType.OTP_SMS, - AuthenticationMethodType.TOTP, - AuthenticationMethodType.U2F, - ]; - - const hasNoMFAMethods = requiredAuthMethodsForForceMFA.every( - (method) => !authmethods.authMethodTypes.includes(method), - ); - - const loginSettings = await getLoginSettings({ - serviceUrl, - organization: session.factors.user.organizationId, - }); + let loginSettings; + try { + loginSettings = await getLoginSettings({ + serviceUrl, + organization: session.factors.user.organizationId, + }); + } catch (error) { + console.error("Error getting login settings:", error); + return { error: "Could not load login settings" }; + } - const forceMfa = !!( - loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly - ); + const forceMfa = !!(loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly); // if the user has no MFA but MFA is enforced, we can set a password otherwise we use the token of the user - if (forceMfa && hasNoMFAMethods) { + if (forceMfa) { + console.log("Set password using service account due to enforced MFA without existing MFA methods"); return setPassword({ serviceUrl, payload }).catch((error) => { // throw error if failed precondition (ex. User is not yet initialized) if (error.code === 9 && error.message) { return { error: "Failed precondition" }; - } else { - throw error; } + return { error: "Could not set password" }; }); } else { const transport = async (serviceUrl: string, token: string) => { @@ -454,10 +451,7 @@ export async function checkSessionAndSetPassword({ return createUserServiceClient(transportPromise); }; - const selfService = await myUserService( - serviceUrl, - `${sessionCookie.token}`, - ); + const selfService = await myUserService(serviceUrl, `${sessionCookie.token}`); return selfService .setPassword( @@ -472,7 +466,7 @@ export async function checkSessionAndSetPassword({ if (error.code === 7) { return { error: "Session is not valid." }; } - throw error; + return { error: "Could not set the password" }; }); } } diff --git a/src/lib/server/register.ts b/src/lib/server/register.ts index f84b4c8d51..74647a3875 100644 --- a/src/lib/server/register.ts +++ b/src/lib/server/register.ts @@ -1,25 +1,16 @@ "use server"; -import { - createSessionAndUpdateCookie, - createSessionForIdpAndUpdateCookie, -} from "@/lib/server/cookie"; -import { - addHumanUser, - addIDPLink, - getLoginSettings, - getUserByID, -} from "@/lib/zitadel"; +import { createSessionAndUpdateCookie, createSessionForIdpAndUpdateCookie } from "@/lib/server/cookie"; +import { addHumanUser, addIDPLink, getLoginSettings, getUserByID, listAuthenticationMethodTypes } from "@/lib/zitadel"; import { create } from "@zitadel/client"; import { Factors } from "@zitadel/proto/zitadel/session/v2/session_pb"; -import { - ChecksJson, - ChecksSchema, -} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; -import { headers } from "next/headers"; -import { getNextUrl } from "../client"; +import { ChecksJson, ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { cookies, headers } from "next/headers"; +import crypto from "crypto"; +import { completeFlowOrGetUrl } from "../client"; import { getServiceUrlFromHeaders } from "../service-url"; -import { checkEmailVerification } from "../verify-helper"; +import { checkEmailVerification, checkMFAFactors } from "../verify-helper"; +import { getOrSetFingerprintId } from "../fingerprint"; type RegisterUserCommand = { email: string; @@ -38,11 +29,6 @@ export type RegisterUserResponse = { export async function registerUser(command: RegisterUserCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } const addResponse = await addHumanUser({ serviceUrl, @@ -78,9 +64,7 @@ export async function registerUser(command: RegisterUserCommand) { const session = await createSessionAndUpdateCookie({ checks, requestId: command.requestId, - lifetime: command.password - ? loginSettings?.passwordCheckLifetime - : undefined, + lifetime: command.password ? loginSettings?.passwordCheckLifetime : undefined, }); if (!session || !session.factors?.user) { @@ -97,6 +81,21 @@ export async function registerUser(command: RegisterUserCommand) { params.append("requestId", command.requestId); } + // Set verification cookie for users registering with passkey (no password) + // This allows them to proceed with passkey registration without additional verification + const cookiesList = await cookies(); + const userAgentId = await getOrSetFingerprintId(); + + const verificationCheck = crypto.createHash("sha256").update(`${session.factors.user.id}:${userAgentId}`).digest("hex"); + + await cookiesList.set({ + name: "verificationCheck", + value: verificationCheck, + httpOnly: true, + path: "/", + maxAge: 300, // 5 minutes + }); + return { redirect: "/passkey/set?" + params }; } else { const userResponse = await getUserByID({ @@ -108,10 +107,7 @@ export async function registerUser(command: RegisterUserCommand) { return { error: "User not found in the system" }; } - const humanUser = - userResponse.user.type.case === "human" - ? userResponse.user.type.value - : undefined; + const humanUser = userResponse.user.type.case === "human" ? userResponse.user.type.value : undefined; const emailVerificationCheck = checkEmailVerification( session, @@ -124,7 +120,7 @@ export async function registerUser(command: RegisterUserCommand) { return emailVerificationCheck; } - const url = await getNextUrl( + return completeFlowOrGetUrl( command.requestId && session.id ? { sessionId: session.id, @@ -137,8 +133,6 @@ export async function registerUser(command: RegisterUserCommand) { }, loginSettings?.defaultRedirectUri, ); - - return { redirect: url }; } } @@ -162,18 +156,11 @@ export type registerUserAndLinkToIDPResponse = { sessionId: string; factors: Factors | undefined; }; -export async function registerUserAndLinkToIDP( - command: RegisterUserAndLinkToIDPommand, -) { +export async function registerUserAndLinkToIDP(command: RegisterUserAndLinkToIDPommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } - - const addResponse = await addHumanUser({ + const addUserResponse = await addHumanUser({ serviceUrl, email: command.email, firstName: command.firstName, @@ -181,7 +168,7 @@ export async function registerUserAndLinkToIDP( organization: command.organization, }); - if (!addResponse) { + if (!addUserResponse) { return { error: "Could not create user" }; } @@ -197,7 +184,7 @@ export async function registerUserAndLinkToIDP( userId: command.idpUserId, userName: command.idpUserName, }, - userId: addResponse.userId, + userId: addUserResponse.userId, }); if (!idpLink) { @@ -206,7 +193,7 @@ export async function registerUserAndLinkToIDP( const session = await createSessionForIdpAndUpdateCookie({ requestId: command.requestId, - userId: addResponse.userId, // the user we just created + userId: addUserResponse.userId, // the user we just created idpIntent: command.idpIntent, lifetime: loginSettings?.externalLoginCheckLifetime, }); @@ -215,7 +202,52 @@ export async function registerUserAndLinkToIDP( return { error: "Could not create session" }; } - const url = await getNextUrl( + // const userResponse = await getUserByID({ + // serviceUrl, + // userId: session?.factors?.user?.id, + // }); + + // if (!userResponse.user) { + // return { error: "User not found in the system" }; + // } + + // const humanUser = userResponse.user.type.case === "human" ? userResponse.user.type.value : undefined; + + // check to see if user was verified + // const emailVerificationCheck = checkEmailVerification(session, humanUser, command.organization, command.requestId); + + // if (emailVerificationCheck?.redirect) { + // return emailVerificationCheck; + // } + + // check if user has MFA methods + let authMethods; + if (session.factors?.user?.id) { + const response = await listAuthenticationMethodTypes({ + serviceUrl, + userId: session.factors.user.id, + }); + if (response.authMethodTypes && response.authMethodTypes.length) { + authMethods = response.authMethodTypes; + } + } + + // Always check MFA factors, even if no auth methods are configured + // This ensures that force MFA settings are respected + const mfaFactorCheck = await checkMFAFactors( + serviceUrl, + session, + loginSettings, + authMethods || [], // Pass empty array if no auth methods + command.organization, + command.requestId, + ); + + if (mfaFactorCheck?.redirect) { + return mfaFactorCheck; + } + + return completeFlowOrGetUrl( command.requestId && session.id ? { sessionId: session.id, @@ -228,6 +260,4 @@ export async function registerUserAndLinkToIDP( }, loginSettings?.defaultRedirectUri, ); - - return { redirect: url }; } diff --git a/src/lib/server/session.ts b/src/lib/server/session.ts index 957c89ad81..59de5aec50 100644 --- a/src/lib/server/session.ts +++ b/src/lib/server/session.ts @@ -13,7 +13,7 @@ import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_p import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { headers } from "next/headers"; -import { getNextUrl } from "../client"; +import { completeFlowOrGetUrl } from "../client"; import { getMostRecentSessionCookie, getSessionCookieById, @@ -21,6 +21,7 @@ import { removeSessionFromCookie, } from "../cookies"; import { getServiceUrlFromHeaders } from "../service-url"; +import { getOriginalHost } from "./host"; export async function skipMFAAndContinueWithNextUrl({ userId, @@ -34,7 +35,7 @@ export async function skipMFAAndContinueWithNextUrl({ sessionId?: string; requestId?: string; organization?: string; -}) { +}): Promise<{ redirect: string } | { error: string }> { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -45,34 +46,31 @@ export async function skipMFAAndContinueWithNextUrl({ await humanMFAInitSkipped({ serviceUrl, userId }); - const url = - requestId && sessionId - ? await getNextUrl( - { - sessionId: sessionId, - requestId: requestId, - organization: organization, - }, - loginSettings?.defaultRedirectUri, - ) - : loginName - ? await getNextUrl( - { - loginName: loginName, - organization: organization, - }, - loginSettings?.defaultRedirectUri, - ) - : null; - if (url) { - return { redirect: url }; + if (requestId && sessionId) { + return completeFlowOrGetUrl( + { + sessionId: sessionId, + requestId: requestId, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ); + } else if (loginName) { + return completeFlowOrGetUrl( + { + loginName: loginName, + organization: organization, + }, + loginSettings?.defaultRedirectUri, + ); } + + return { error: "Could not skip MFA and continue" }; } -export async function continueWithSession({ - requestId, - ...session -}: Session & { requestId?: string }) { +export type ContinueWithSessionCommand = Session & { requestId?: string }; + +export async function continueWithSession({ requestId, ...session }: ContinueWithSessionCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -81,27 +79,23 @@ export async function continueWithSession({ organization: session.factors?.user?.organizationId, }); - const url = - requestId && session.id && session.factors?.user - ? await getNextUrl( - { - sessionId: session.id, - requestId: requestId, - organization: session.factors.user.organizationId, - }, - loginSettings?.defaultRedirectUri, - ) - : session.factors?.user - ? await getNextUrl( - { - loginName: session.factors.user.loginName, - organization: session.factors.user.organizationId, - }, - loginSettings?.defaultRedirectUri, - ) - : null; - if (url) { - return { redirect: url }; + if (requestId && session.id && session.factors?.user) { + return completeFlowOrGetUrl( + { + sessionId: session.id, + requestId: requestId, + organization: session.factors.user.organizationId, + }, + loginSettings?.defaultRedirectUri, + ); + } else if (session.factors?.user) { + return completeFlowOrGetUrl( + { + loginName: session.factors.user.loginName, + organization: session.factors.user.organizationId, + }, + loginSettings?.defaultRedirectUri, + ); } } @@ -116,8 +110,7 @@ export type UpdateSessionCommand = { }; export async function updateSession(options: UpdateSessionCommand) { - let { loginName, sessionId, organization, checks, requestId, challenges } = - options; + let { loginName, sessionId, organization, checks, requestId, challenges } = options; const recentSession = sessionId ? await getSessionCookieById({ sessionId }) : loginName @@ -132,18 +125,13 @@ export async function updateSession(options: UpdateSessionCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); + const host = await getOriginalHost(); if (!host) { return { error: "Could not get host" }; } - if ( - host && - challenges && - challenges.webAuthN && - !challenges.webAuthN.domain - ) { + if (host && challenges && challenges.webAuthN && !challenges.webAuthN.domain) { const [hostname] = host.split(":"); challenges.webAuthN.domain = hostname; @@ -219,11 +207,11 @@ export async function clearSession(options: ClearSessionOptions) { }); const securitySettings = await getSecuritySettings({ serviceUrl }); - const sameSite = securitySettings?.embeddedIframe?.enabled ? "none" : true; + const iFrameEnabled = !!securitySettings?.embeddedIframe?.enabled; if (!deleteResponse) { throw new Error("Could not delete session"); } - return removeSessionFromCookie({ session: sessionCookie, sameSite }); + return removeSessionFromCookie({ session: sessionCookie, iFrameEnabled }); } diff --git a/src/lib/server/u2f.ts b/src/lib/server/u2f.ts index 70eac14e55..69d0454e90 100644 --- a/src/lib/server/u2f.ts +++ b/src/lib/server/u2f.ts @@ -7,6 +7,7 @@ import { headers } from "next/headers"; import { userAgent } from "next/server"; import { getSessionCookieById } from "../cookies"; import { getServiceUrlFromHeaders } from "../service-url"; +import { getOriginalHost } from "./host"; type RegisterU2FCommand = { sessionId: string; @@ -22,11 +23,7 @@ type VerifyU2FCommand = { export async function addU2F(command: RegisterU2FCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } + const host = await getOriginalHost(); const sessionCookie = await getSessionCookieById({ sessionId: command.sessionId, @@ -60,12 +57,6 @@ export async function addU2F(command: RegisterU2FCommand) { export async function verifyU2F(command: VerifyU2FCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host || typeof host !== "string") { - throw new Error("No host found"); - } - let passkeyName = command.passkeyName; if (!passkeyName) { const headersList = await headers(); diff --git a/src/lib/server/verify.ts b/src/lib/server/verify.ts index cf60f739b3..8326c428d4 100644 --- a/src/lib/server/verify.ts +++ b/src/lib/server/verify.ts @@ -17,19 +17,16 @@ import { create } from "@zitadel/client"; import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { cookies, headers } from "next/headers"; -import { getNextUrl } from "../client"; +import { completeFlowOrGetUrl } from "../client"; import { getSessionCookieByLoginName } from "../cookies"; import { getOrSetFingerprintId } from "../fingerprint"; import { getServiceUrlFromHeaders } from "../service-url"; import { loadMostRecentSession } from "../session"; import { checkMFAFactors } from "../verify-helper"; import { createSessionAndUpdateCookie } from "./cookie"; +import { getOriginalHostWithProtocol } from "./host"; -export async function verifyTOTP( - code: string, - loginName?: string, - organization?: string, -) { +export async function verifyTOTP(code: string, loginName?: string, organization?: string) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); @@ -104,8 +101,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { const user = userResponse.user; const sessionCookie = await getSessionCookieByLoginName({ - loginName: - "loginName" in command ? command.loginName : user.preferredLoginName, + loginName: "loginName" in command ? command.loginName : user.preferredLoginName, organization: command.organization, }).catch((error) => { console.warn("Ignored error:", error); // checked later @@ -134,11 +130,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { } // if no authmethods are found on the user, redirect to set one up - if ( - authMethodResponse && - authMethodResponse.authMethodTypes && - authMethodResponse.authMethodTypes.length == 0 - ) { + if (authMethodResponse && authMethodResponse.authMethodTypes && authMethodResponse.authMethodTypes.length == 0) { if (!sessionCookie) { const checks = create(ChecksSchema, { user: { @@ -171,10 +163,7 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { const cookiesList = await cookies(); const userAgentId = await getOrSetFingerprintId(); - const verificationCheck = crypto - .createHash("sha256") - .update(`${user.userId}:${userAgentId}`) - .digest("hex"); + const verificationCheck = crypto.createHash("sha256").update(`${user.userId}:${userAgentId}`).digest("hex"); await cookiesList.set({ name: "verificationCheck", @@ -196,15 +185,10 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { verifySuccessParams.set("userId", command.userId); } - if ( - ("loginName" in command && command.loginName) || - user.preferredLoginName - ) { + if (("loginName" in command && command.loginName) || user.preferredLoginName) { verifySuccessParams.set( "loginName", - "loginName" in command && command.loginName - ? command.loginName - : user.preferredLoginName, + "loginName" in command && command.loginName ? command.loginName : user.preferredLoginName, ); } if (command.requestId) { @@ -238,28 +222,24 @@ export async function sendVerification(command: VerifyUserByEmailCommand) { // login user if no additional steps are required if (command.requestId && session.id) { - const nextUrl = await getNextUrl( + return completeFlowOrGetUrl( { sessionId: session.id, requestId: command.requestId, - organization: - command.organization ?? session.factors?.user?.organizationId, + organization: command.organization ?? session.factors?.user?.organizationId, }, loginSettings?.defaultRedirectUri, ); - - return { redirect: nextUrl }; } - const url = await getNextUrl( + // Regular flow - return URL for client-side navigation + return completeFlowOrGetUrl( { loginName: session.factors.user.loginName, organization: session.factors?.user?.organizationId, }, loginSettings?.defaultRedirectUri, ); - - return { redirect: url }; } type resendVerifyEmailCommand = { @@ -271,11 +251,7 @@ type resendVerifyEmailCommand = { export async function resendVerification(command: resendVerifyEmailCommand) { const _headers = await headers(); const { serviceUrl } = getServiceUrlFromHeaders(_headers); - const host = _headers.get("host"); - - if (!host) { - return { error: "No host found" }; - } + const hostWithProtocol = await getOriginalHostWithProtocol(); const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? ""; @@ -284,7 +260,7 @@ export async function resendVerification(command: resendVerifyEmailCommand) { serviceUrl, userId: command.userId, urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + + `${hostWithProtocol}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}&invite=true` + (command.requestId ? `&requestId=${command.requestId}` : ""), }).catch((error) => { if (error.code === 9) { @@ -296,7 +272,7 @@ export async function resendVerification(command: resendVerifyEmailCommand) { userId: command.userId, serviceUrl, urlTemplate: - `${host.includes("localhost") ? "http://" : "https://"}${host}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + + `${hostWithProtocol}${basePath}/verify?code={{.Code}}&userId={{.UserID}}&organization={{.OrgID}}` + (command.requestId ? `&requestId=${command.requestId}` : ""), }); } diff --git a/src/lib/session.test.ts b/src/lib/session.test.ts new file mode 100644 index 0000000000..09679ec2b1 --- /dev/null +++ b/src/lib/session.test.ts @@ -0,0 +1,1147 @@ +/** + * Unit tests for the isSessionValid function. + * + * This test suite covers the comprehensive session validation logic including: + * - Session expiration checks + * - User presence validation + * - Authentication factor verification (password, passkey, IDP) + * - MFA validation using the shared shouldEnforceMFA function from verify-helper + * - Passkey authentication inherently satisfies MFA requirements + * - MFA validation with configured authentication methods (TOTP, OTP Email/SMS, U2F) + * - MFA validation with login settings (forceMfa, forceMfaLocalOnly) + * - Email verification when EMAIL_VERIFICATION environment variable is enabled + * - Edge cases like sessions without expiration date + */ + +import { timestampDate } from "@zitadel/client"; +import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { isSessionValid } from "./session"; +import * as verifyHelperModule from "./verify-helper"; +import * as zitadelModule from "./zitadel"; + +// Mock the zitadel client timestampDate function +vi.mock("@zitadel/client", () => ({ + timestampDate: vi.fn(), +})); + +// Mock the zitadel module +vi.mock("./zitadel", () => ({ + listAuthenticationMethodTypes: vi.fn(), + getLoginSettings: vi.fn(), + getUserByID: vi.fn(), +})); + +// Mock the verify-helper module +vi.mock("./verify-helper", () => ({ + shouldEnforceMFA: vi.fn(), +})); + +// Mock environment variables +const originalEnv = process.env; + +describe("isSessionValid", () => { + const mockServiceUrl = "https://zitadel-abc123.zitadel.cloud"; + const mockUserId = "test-user-id"; + const mockOrganizationId = "test-org-id"; + + beforeEach(() => { + vi.clearAllMocks(); + process.env = { ...originalEnv }; + // @ts-ignore - delete is OK for test environment variables + delete process.env.EMAIL_VERIFICATION; + + // Setup timestampDate mock to return valid dates + vi.mocked(timestampDate).mockImplementation((timestamp: any) => { + if (!timestamp || !timestamp.seconds) { + return new Date(); // Return current date for invalid timestamps + } + return new Date(Number(timestamp.seconds) * 1000); + }); + }); + + afterEach(() => { + process.env = originalEnv; + }); + + const createMockTimestamp = (offsetMs = 3600000): any => ({ + seconds: BigInt(Math.floor((Date.now() + offsetMs) / 1000)), + }); + + const createMockSession = (overrides: any = {}): any => { + const futureTimestamp = createMockTimestamp(); + + const defaultSession = { + id: "session-id", + expirationDate: futureTimestamp, + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: futureTimestamp, + }, + password: { + verifiedAt: futureTimestamp, + }, + }, + ...overrides, + }; + + return defaultSession; + }; + + describe("when session has no user", () => { + test("should return false and log warning", async () => { + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const session = createMockSession({ + factors: undefined, + }); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + expect(consoleSpy).toHaveBeenCalledWith("Session has no user"); + consoleSpy.mockRestore(); + }); + }); + + describe("when session is expired", () => { + test("should return false and log warning", async () => { + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const session = createMockSession({ + expirationDate: createMockTimestamp(-3600000), // 1 hour ago + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + expect(consoleSpy).toHaveBeenCalledWith("Session is expired", expect.any(String)); + consoleSpy.mockRestore(); + }); + }); + + describe("when session has no valid authentication factors", () => { + test("should return false when no password, passkey, or IDP verification", async () => { + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: createMockTimestamp(), + }, + // No password, webAuthN, or intent factors + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + }); + }); + + describe("MFA validation with configured authentication methods", () => { + test("should return true when TOTP is configured and verified with MFA required", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + totp: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return true when TOTP is configured but not verified and MFA is not required", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // No TOTP verification + }, + }); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return false when TOTP is configured but not verified and MFA is required", async () => { + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // No TOTP verification + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.PASSWORD, AuthenticationMethodType.TOTP], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(true); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + expect(consoleSpy).toHaveBeenCalledWith("Session has no valid MFA factor. Configured methods:", expect.any(Array), "Session factors:", expect.any(Object)); + consoleSpy.mockRestore(); + }); + + test("should return true when OTP Email is configured and verified with MFA required", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + otpEmail: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.OTP_EMAIL], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return true when U2F is configured and verified", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + webAuthN: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.U2F], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return true when multiple auth methods are configured and one is verified with MFA required", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + otpEmail: { + verifiedAt: verifiedTimestamp, + }, + // TOTP not verified + }, + }); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.TOTP, AuthenticationMethodType.OTP_EMAIL], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return true when session has only password and MFA is not required by policy", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // No MFA factors verified + }, + }); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return true when user has PASSWORD and TOTP configured but only password verified and MFA not required", async () => { + // This test specifically covers the original bug scenario: + // - User has PASSWORD and TOTP configured (would show up in listAuthenticationMethodTypes) + // - User has only verified password, not TOTP + // - MFA is not required by policy + // - Session should be valid (this was the bug - it was returning false) + + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // TOTP is configured but NOT verified - this is the key part + // totp: undefined (no verifiedAt) + }, + }); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return false when user has PASSWORD and TOTP configured but only password verified and MFA IS required", async () => { + // This is the counterpart test to ensure MFA is still enforced when required + + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // TOTP is configured but NOT verified + // totp: undefined (no verifiedAt) + }, + }); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.TOTP], + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(true); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + expect(consoleSpy).toHaveBeenCalledWith( + "Session has no valid MFA factor. Configured methods:", + [AuthenticationMethodType.TOTP], + "Session factors:", + expect.objectContaining({ + totp: undefined, + otpEmail: undefined, + otpSms: undefined, + webAuthN: undefined, + }), + ); + consoleSpy.mockRestore(); + }); + + test("REGRESSION TEST: user with only PASSWORD factor should be valid when MFA not required", async () => { + // This test specifically verifies the original bug is fixed + // Original bug: A user with only PASSWORD authentication would be invalid + // because the code checked if authMethods.length > 0 (which included PASSWORD) + // and then required MFA verification even when MFA was not required by policy + + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // Explicitly no MFA factors at all + totp: undefined, + otpEmail: undefined, + otpSms: undefined, + webAuthN: undefined, + intent: undefined, + }, + }); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + // This should be true - if it's false, the original bug still exists + expect(result).toBe(true); + }); + + test("DEMONSTRATION: how the original bug would manifest with old logic", async () => { + // This test demonstrates the original problematic scenario: + // 1. listAuthenticationMethodTypes returns [PASSWORD, TOTP] + // 2. Old logic would check authMethods.length > 0 (true because PASSWORD is included) + // 3. Old logic would then require MFA verification regardless of policy + // 4. User has only password verified, no TOTP + // 5. Session would be marked invalid even though MFA is not required + + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // User has TOTP configured but not verified + totp: undefined, + }, + }); + + // MFA is NOT required by policy + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + // With our fix, this should be true (session is valid) + // With the old logic, this would have been false (bug) + expect(result).toBe(true); + }); + }); + + describe("MFA validation with login settings (no configured auth methods)", () => { + test("should return true when MFA is not forced and no auth methods configured", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return false when MFA is forced but no factors are verified", async () => { + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // No MFA factors verified + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(true); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + expect(consoleSpy).toHaveBeenCalledWith("Session has no valid multifactor", expect.any(Object)); + consoleSpy.mockRestore(); + }); + + test("should return true when MFA is forced and TOTP is verified", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + totp: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return true when forceMfaLocalOnly is enabled and WebAuthn is verified", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + webAuthN: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: true, + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return false when forceMfaLocalOnly is enabled for password authentication but MFA not satisfied", async () => { + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + // No MFA factors verified + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.TOTP], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: true, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(true); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + expect(zitadelModule.getLoginSettings).toHaveBeenCalledWith({ + serviceUrl: mockServiceUrl, + organization: mockOrganizationId, + }); + expect(zitadelModule.listAuthenticationMethodTypes).toHaveBeenCalledWith({ + serviceUrl: mockServiceUrl, + userId: mockUserId, + }); + consoleSpy.mockRestore(); + }); + }); + + describe("email verification", () => { + test("should return false when EMAIL_VERIFICATION is enabled and email is not verified", async () => { + process.env.EMAIL_VERIFICATION = "true"; + const consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); + + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + vi.mocked(zitadelModule.getUserByID).mockResolvedValue({ + user: { + type: { + case: "human", + value: { + email: { + email: "test@example.com", + isVerified: false, + }, + }, + }, + }, + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + expect(consoleSpy).toHaveBeenCalledWith( + "Session invalid: Email not verified and EMAIL_VERIFICATION is enabled", + mockUserId, + ); + consoleSpy.mockRestore(); + }); + + test("should return true when EMAIL_VERIFICATION is enabled and email is verified", async () => { + process.env.EMAIL_VERIFICATION = "true"; + + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + vi.mocked(zitadelModule.getUserByID).mockResolvedValue({ + user: { + type: { + case: "human", + value: { + email: { + email: "test@example.com", + isVerified: true, + }, + }, + }, + }, + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + + test("should return true when EMAIL_VERIFICATION is disabled", async () => { + // EMAIL_VERIFICATION is not set, so it's disabled by default + + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + // getUserByID should not be called when EMAIL_VERIFICATION is disabled + expect(zitadelModule.getUserByID).not.toHaveBeenCalled(); + }); + }); + + describe("passkey authentication", () => { + test("should return true when authenticated with passkey (WebAuthn)", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + webAuthN: { + verifiedAt: verifiedTimestamp, + }, + // No password factor + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + }); + + describe("IDP authentication", () => { + test("should return true when authenticated with IDP intent and no MFA required", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + intent: { + verifiedAt: verifiedTimestamp, + }, + // No password factor + }, + }); + + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + expect(verifyHelperModule.shouldEnforceMFA).toHaveBeenCalledWith(session, expect.any(Object)); + expect(zitadelModule.getLoginSettings).toHaveBeenCalledWith({ + serviceUrl: mockServiceUrl, + organization: mockOrganizationId, + }); + }); + + test("should return false when authenticated with IDP intent but MFA required and not satisfied", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + intent: { + verifiedAt: verifiedTimestamp, + }, + // No password factor, no MFA factors verified + }, + }); + + // shouldEnforceMFA returns true (MFA is required for this session) + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(true); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + // User has MFA methods configured but none verified + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.TOTP, AuthenticationMethodType.OTP_EMAIL], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(false); + expect(verifyHelperModule.shouldEnforceMFA).toHaveBeenCalledWith(session, expect.any(Object)); + expect(zitadelModule.getLoginSettings).toHaveBeenCalledWith({ + serviceUrl: mockServiceUrl, + organization: mockOrganizationId, + }); + expect(zitadelModule.listAuthenticationMethodTypes).toHaveBeenCalledWith({ + serviceUrl: mockServiceUrl, + userId: mockUserId, + }); + }); + + test("should return true when authenticated with IDP intent and forceMfaLocalOnly (IDP bypasses local-only MFA)", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + intent: { + verifiedAt: verifiedTimestamp, + }, + // No password factor, no MFA factors verified + }, + }); + + // shouldEnforceMFA returns false (IDP bypasses forceMfaLocalOnly) + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: true, + } as any); + + // User has MFA methods configured but none verified + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.TOTP, AuthenticationMethodType.OTP_EMAIL], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + expect(verifyHelperModule.shouldEnforceMFA).toHaveBeenCalledWith(session, expect.any(Object)); + expect(zitadelModule.getLoginSettings).toHaveBeenCalledWith({ + serviceUrl: mockServiceUrl, + organization: mockOrganizationId, + }); + // Should not call listAuthenticationMethodTypes since shouldEnforceMFA returned false + expect(zitadelModule.listAuthenticationMethodTypes).not.toHaveBeenCalled(); + }); + + test("should return true when authenticated with IDP intent and MFA required and satisfied", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + intent: { + verifiedAt: verifiedTimestamp, + }, + totp: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + // Organization enforces MFA + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + // User has TOTP configured and verified + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.TOTP], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + }); + + describe("passkey authentication", () => { + test("should return true when authenticated with passkey and MFA required (passkey satisfies MFA)", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + webAuthN: { + verifiedAt: verifiedTimestamp, + }, + // No password factor, no additional MFA factors + }, + }); + + // shouldEnforceMFA returns false (passkey satisfies MFA requirements) + vi.mocked(verifyHelperModule.shouldEnforceMFA).mockReturnValue(false); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: true, + forceMfaLocalOnly: false, + } as any); + + // User has MFA methods configured but none verified (passkey should satisfy MFA) + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [AuthenticationMethodType.TOTP], + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + expect(verifyHelperModule.shouldEnforceMFA).toHaveBeenCalledWith(session, expect.any(Object)); + expect(zitadelModule.getLoginSettings).toHaveBeenCalledWith({ + serviceUrl: mockServiceUrl, + organization: mockOrganizationId, + }); + // Should not call listAuthenticationMethodTypes since shouldEnforceMFA returned false + expect(zitadelModule.listAuthenticationMethodTypes).not.toHaveBeenCalled(); + }); + }); + + describe("edge cases", () => { + test("should handle session without expiration date", async () => { + const verifiedTimestamp = createMockTimestamp(); + const session = createMockSession({ + expirationDate: undefined, // No expiration date + factors: { + user: { + id: mockUserId, + organizationId: mockOrganizationId, + loginName: "test@example.com", + displayName: "Test User", + verifiedAt: verifiedTimestamp, + }, + password: { + verifiedAt: verifiedTimestamp, + }, + }, + }); + + vi.mocked(zitadelModule.listAuthenticationMethodTypes).mockResolvedValue({ + authMethodTypes: [], + } as any); + + vi.mocked(zitadelModule.getLoginSettings).mockResolvedValue({ + forceMfa: false, + forceMfaLocalOnly: false, + } as any); + + const result = await isSessionValid({ serviceUrl: mockServiceUrl, session }); + + expect(result).toBe(true); + }); + }); +}); diff --git a/src/lib/session.ts b/src/lib/session.ts index 8c2548b8fb..83bca3d15b 100644 --- a/src/lib/session.ts +++ b/src/lib/session.ts @@ -5,11 +5,8 @@ import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb"; import { GetSessionResponse } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb"; import { getMostRecentCookieWithLoginname } from "./cookies"; -import { - getLoginSettings, - getSession, - listAuthenticationMethodTypes, -} from "./zitadel"; +import { shouldEnforceMFA } from "./verify-helper"; +import { getLoginSettings, getSession, getUserByID, listAuthenticationMethodTypes } from "./zitadel"; type LoadMostRecentSessionParams = { serviceUrl: string; @@ -39,13 +36,7 @@ export async function loadMostRecentSession({ * mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.) * to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId); **/ -export async function isSessionValid({ - serviceUrl, - session, -}: { - serviceUrl: string; - session: Session; -}): Promise { +export async function isSessionValid({ serviceUrl, session }: { serviceUrl: string; session: Session }): Promise { // session can't be checked without user if (!session.factors?.user) { console.warn("Session has no user"); @@ -54,96 +45,109 @@ export async function isSessionValid({ let mfaValid = true; - const authMethodTypes = await listAuthenticationMethodTypes({ + // Check if user authenticated via different methods + const validIDP = session?.factors?.intent?.verifiedAt; + const validPassword = session?.factors?.password?.verifiedAt; + const validPasskey = session?.factors?.webAuthN?.verifiedAt; + + // Get login settings to determine if MFA is actually required by policy + const loginSettings = await getLoginSettings({ serviceUrl, - userId: session.factors.user.id, + organization: session.factors?.user?.organizationId, }); - const authMethods = authMethodTypes.authMethodTypes; - if (authMethods && authMethods.includes(AuthenticationMethodType.TOTP)) { - mfaValid = !!session.factors.totp?.verifiedAt; - if (!mfaValid) { - console.warn( - "Session has no valid totpEmail factor", - session.factors.totp?.verifiedAt, - ); - } - } else if ( - authMethods && - authMethods.includes(AuthenticationMethodType.OTP_EMAIL) - ) { - mfaValid = !!session.factors.otpEmail?.verifiedAt; - if (!mfaValid) { - console.warn( - "Session has no valid otpEmail factor", - session.factors.otpEmail?.verifiedAt, - ); - } - } else if ( - authMethods && - authMethods.includes(AuthenticationMethodType.OTP_SMS) - ) { - mfaValid = !!session.factors.otpSms?.verifiedAt; - if (!mfaValid) { - console.warn( - "Session has no valid otpSms factor", - session.factors.otpSms?.verifiedAt, - ); - } - } else if ( - authMethods && - authMethods.includes(AuthenticationMethodType.U2F) - ) { - mfaValid = !!session.factors.webAuthN?.verifiedAt; - if (!mfaValid) { - console.warn( - "Session has no valid u2f factor", - session.factors.webAuthN?.verifiedAt, - ); - } - } else { - // only check settings if no auth methods are available, as this would require a setup - const loginSettings = await getLoginSettings({ + // Use the existing shouldEnforceMFA function to determine if MFA is required + const isMfaRequired = shouldEnforceMFA(session, loginSettings); + + // Only enforce MFA validation if MFA is required by policy + if (isMfaRequired) { + const authMethodTypes = await listAuthenticationMethodTypes({ serviceUrl, - organization: session.factors?.user?.organizationId, + userId: session.factors.user.id, }); - if (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) { + + const authMethods = authMethodTypes.authMethodTypes; + // Filter to only MFA methods (exclude PASSWORD and PASSKEY) + const mfaMethods = authMethods?.filter( + (method) => + method === AuthenticationMethodType.TOTP || + method === AuthenticationMethodType.OTP_EMAIL || + method === AuthenticationMethodType.OTP_SMS || + method === AuthenticationMethodType.U2F, + ); + + if (mfaMethods && mfaMethods.length > 0) { + // Check if any of the configured MFA methods have been verified + const totpValid = mfaMethods.includes(AuthenticationMethodType.TOTP) && !!session.factors.totp?.verifiedAt; + const otpEmailValid = + mfaMethods.includes(AuthenticationMethodType.OTP_EMAIL) && !!session.factors.otpEmail?.verifiedAt; + const otpSmsValid = mfaMethods.includes(AuthenticationMethodType.OTP_SMS) && !!session.factors.otpSms?.verifiedAt; + const u2fValid = mfaMethods.includes(AuthenticationMethodType.U2F) && !!session.factors.webAuthN?.verifiedAt; + + mfaValid = totpValid || otpEmailValid || otpSmsValid || u2fValid; + + if (!mfaValid) { + console.warn("Session has no valid MFA factor. Configured methods:", mfaMethods, "Session factors:", { + totp: session.factors.totp?.verifiedAt, + otpEmail: session.factors.otpEmail?.verifiedAt, + otpSms: session.factors.otpSms?.verifiedAt, + webAuthN: session.factors.webAuthN?.verifiedAt, + }); + } + } else { + // No specific MFA methods configured, but MFA is forced - check for any verified MFA factors + // (excluding IDP which should be handled separately) const otpEmail = session.factors.otpEmail?.verifiedAt; const otpSms = session.factors.otpSms?.verifiedAt; const totp = session.factors.totp?.verifiedAt; const webAuthN = session.factors.webAuthN?.verifiedAt; - const idp = session.factors.intent?.verifiedAt; // TODO: forceMFA should not consider this as valid factor + // Note: Removed IDP (session.factors.intent?.verifiedAt) as requested - // must have one single check - mfaValid = !!(otpEmail || otpSms || totp || webAuthN || idp); + mfaValid = !!(otpEmail || otpSms || totp || webAuthN); if (!mfaValid) { console.warn("Session has no valid multifactor", session.factors); } - } else { - mfaValid = true; } } - const validPassword = session?.factors?.password?.verifiedAt; - const validPasskey = session?.factors?.webAuthN?.verifiedAt; - const validIDP = session?.factors?.intent?.verifiedAt; + // If MFA is not required by policy, mfaValid remains true - const stillValid = session.expirationDate - ? timestampDate(session.expirationDate).getTime() > new Date().getTime() - : true; + const stillValid = session.expirationDate ? timestampDate(session.expirationDate).getTime() > new Date().getTime() : true; if (!stillValid) { console.warn( "Session is expired", - session.expirationDate - ? timestampDate(session.expirationDate).toDateString() - : "no expiration date", + session.expirationDate ? timestampDate(session.expirationDate).toDateString() : "no expiration date", ); + return false; } const validChecks = !!(validPassword || validPasskey || validIDP); - return stillValid && validChecks && mfaValid; + if (!validChecks) { + return false; + } + + if (!mfaValid) { + return false; + } + + // Check email verification if EMAIL_VERIFICATION environment variable is enabled + if (process.env.EMAIL_VERIFICATION === "true") { + const userResponse = await getUserByID({ + serviceUrl, + userId: session.factors.user.id, + }); + + const humanUser = userResponse?.user?.type.case === "human" ? userResponse?.user.type.value : undefined; + + if (humanUser && !humanUser.email?.isVerified) { + console.warn("Session invalid: Email not verified and EMAIL_VERIFICATION is enabled", session.factors.user.id); + return false; + } + } + + return true; } export async function findValidSession({ @@ -165,7 +169,8 @@ export async function findValidSession({ return s.factors?.user?.loginName === authRequest.loginHint; } if (samlRequest) { - // TODO: do whatever + // SAML requests don't contain user hints like OIDC (hintUserId/loginHint) + // so we return all sessions for further processing return true; } return true; diff --git a/src/lib/verify-helper.test.ts b/src/lib/verify-helper.test.ts new file mode 100644 index 0000000000..8094b6570d --- /dev/null +++ b/src/lib/verify-helper.test.ts @@ -0,0 +1,322 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { shouldEnforceMFA } from "./verify-helper"; + +// Mock function to create timestamps - following the same pattern as session.test.ts +function createMockTimestamp(offsetMs = 3600000): any { + return { + seconds: BigInt(Math.floor((Date.now() + offsetMs) / 1000)), + nanos: 0, + }; +} + +// Mock function to create a basic session - following the same pattern as session.test.ts +function createMockSession(overrides: any = {}): any { + const futureTimestamp = createMockTimestamp(); + + const defaultSession = { + id: "test-session-id", + factors: { + user: { + id: "test-user-id", + loginName: "test@example.com", + displayName: "Test User", + organizationId: "test-org-id", + verifiedAt: futureTimestamp, + }, + }, + ...overrides, + }; + + return defaultSession; +} + +// Mock function to create login settings +function createMockLoginSettings(overrides: any = {}): any { + return { + forceMfa: false, + forceMfaLocalOnly: false, + ...overrides, + }; +} + +describe("shouldEnforceMFA", () => { + let mockSession: any; + let mockLoginSettings: any; + + beforeEach(() => { + mockSession = createMockSession(); + mockLoginSettings = createMockLoginSettings(); + }); + + describe("when loginSettings is undefined", () => { + it("should return false", () => { + const result = shouldEnforceMFA(mockSession, undefined); + expect(result).toBe(false); + }); + }); + + describe("passkey authentication", () => { + beforeEach(() => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + webAuthN: { + verifiedAt: createMockTimestamp(), + userVerified: true, + }, + }, + }); + }); + + it("should return false when user authenticated with passkey, even with forceMfa enabled", () => { + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); + }); + + it("should return false when user authenticated with passkey, even with forceMfaLocalOnly enabled", () => { + mockLoginSettings = createMockLoginSettings({ forceMfaLocalOnly: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); + }); + + it("should return false when user authenticated with passkey and both force settings enabled", () => { + mockLoginSettings = createMockLoginSettings({ + forceMfa: true, + forceMfaLocalOnly: true, + }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); + }); + + it("should return true when passkey is not user verified", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + webAuthN: { + verifiedAt: createMockTimestamp(), + userVerified: false, // Not user verified + }, + }, + }); + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + // Should return true because passkey is not user verified, so it doesn't count as passkey auth + expect(result).toBe(true); + }); + }); + + describe("forceMfa setting", () => { + beforeEach(() => { + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + }); + + it("should return true when forceMfa is enabled and user authenticated with password", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + password: { + verifiedAt: createMockTimestamp(), + }, + }, + }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); + }); + + it("should return true when forceMfa is enabled and user authenticated with IDP", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + intent: { + verifiedAt: createMockTimestamp(), + }, + }, + }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); + }); + + it("should return true when forceMfa is enabled with no specific authentication method", () => { + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); + }); + }); + + describe("forceMfaLocalOnly setting", () => { + beforeEach(() => { + mockLoginSettings = createMockLoginSettings({ forceMfaLocalOnly: true }); + }); + + it("should return true when forceMfaLocalOnly is enabled and user authenticated with password", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + password: { + verifiedAt: createMockTimestamp(), + }, + }, + }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); + }); + + it("should return false when forceMfaLocalOnly is enabled and user authenticated with IDP", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + intent: { + verifiedAt: createMockTimestamp(), + }, + }, + }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); + }); + + it("should return false when forceMfaLocalOnly is enabled with no specific authentication method", () => { + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); + }); + }); + + describe("mixed authentication scenarios", () => { + it("should prioritize passkey over password when both are present", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + password: { + verifiedAt: createMockTimestamp(), + }, + webAuthN: { + verifiedAt: createMockTimestamp(), + userVerified: true, + }, + }, + }); + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); // Passkey should override password + }); + + it("should prioritize passkey over IDP when both are present", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + intent: { + verifiedAt: createMockTimestamp(), + }, + webAuthN: { + verifiedAt: createMockTimestamp(), + userVerified: true, + }, + }, + }); + mockLoginSettings = createMockLoginSettings({ forceMfaLocalOnly: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); // Passkey should override IDP + }); + + it("should handle password + IDP scenario with forceMfaLocalOnly", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + password: { + verifiedAt: createMockTimestamp(), + }, + intent: { + verifiedAt: createMockTimestamp(), + }, + }, + }); + mockLoginSettings = createMockLoginSettings({ forceMfaLocalOnly: true }); + // With both password and IDP, the current logic should return false for IDP + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); + }); + }); + + describe("no MFA enforcement", () => { + it("should return false when neither forceMfa nor forceMfaLocalOnly is enabled", () => { + mockLoginSettings = createMockLoginSettings({ + forceMfa: false, + forceMfaLocalOnly: false, + }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(false); + }); + }); + + describe("edge cases", () => { + it("should handle session with no factors", () => { + mockSession = createMockSession({ + factors: undefined, + }); + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); + }); + + it("should handle session with empty factors", () => { + mockSession = createMockSession({ + factors: { + user: { + id: "test-user-id", + loginName: "test@example.com", + displayName: "Test User", + organizationId: "test-org-id", + verifiedAt: createMockTimestamp(), + }, + }, + }); + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); + }); + + it("should handle webAuthN factor without userVerified", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + webAuthN: { + verifiedAt: createMockTimestamp(), + userVerified: false, + }, + }, + }); + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); // Should require MFA since it's not a proper passkey + }); + + it("should handle webAuthN factor without verifiedAt", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + webAuthN: { + userVerified: true, + // verifiedAt is undefined + }, + }, + }); + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); // Should require MFA since webAuthN wasn't actually verified + }); + + it("should handle webAuthN factor with verifiedAt but no userVerified property", () => { + mockSession = createMockSession({ + factors: { + ...mockSession.factors, + webAuthN: { + verifiedAt: createMockTimestamp(), + // userVerified is undefined (should be falsy) + }, + }, + }); + mockLoginSettings = createMockLoginSettings({ forceMfa: true }); + const result = shouldEnforceMFA(mockSession, mockLoginSettings); + expect(result).toBe(true); // Should require MFA since userVerified is falsy + }); + }); +}); diff --git a/src/lib/verify-helper.ts b/src/lib/verify-helper.ts index dbd9b2796b..161ed402a1 100644 --- a/src/lib/verify-helper.ts +++ b/src/lib/verify-helper.ts @@ -20,9 +20,7 @@ export function checkPasswordChangeRequired( let isOutdated = false; if (expirySettings?.maxAgeDays && humanUser?.passwordChanged) { const maxAgeDays = Number(expirySettings.maxAgeDays); // Convert bigint to number - const passwordChangedDate = moment( - timestampDate(humanUser.passwordChanged), - ); + const passwordChangedDate = moment(timestampDate(humanUser.passwordChanged)); const outdatedPassword = passwordChangedDate.add(maxAgeDays, "days"); isOutdated = moment().isAfter(outdatedPassword); } @@ -33,10 +31,7 @@ export function checkPasswordChangeRequired( }); if (organization || session.factors?.user?.organizationId) { - params.append( - "organization", - session.factors?.user?.organizationId as string, - ); + params.append("organization", session.factors?.user?.organizationId as string); } if (requestId) { @@ -47,12 +42,7 @@ export function checkPasswordChangeRequired( } } -export function checkEmailVerified( - session: Session, - humanUser?: HumanUser, - organization?: string, - requestId?: string, -) { +export function checkEmailVerified(session: Session, humanUser?: HumanUser, organization?: string, requestId?: string) { if (!humanUser?.email?.isVerified) { const paramsVerify = new URLSearchParams({ loginName: session.factors?.user?.loginName as string, @@ -61,10 +51,7 @@ export function checkEmailVerified( }); if (organization || session.factors?.user?.organizationId) { - paramsVerify.append( - "organization", - organization ?? (session.factors?.user?.organizationId as string), - ); + paramsVerify.append("organization", organization ?? (session.factors?.user?.organizationId as string)); } if (requestId) { @@ -75,16 +62,8 @@ export function checkEmailVerified( } } -export function checkEmailVerification( - session: Session, - humanUser?: HumanUser, - organization?: string, - requestId?: string, -) { - if ( - !humanUser?.email?.isVerified && - process.env.EMAIL_VERIFICATION === "true" - ) { +export function checkEmailVerification(session: Session, humanUser?: HumanUser, organization?: string, requestId?: string) { + if (!humanUser?.email?.isVerified && process.env.EMAIL_VERIFICATION === "true") { const params = new URLSearchParams({ loginName: session.factors?.user?.loginName as string, send: "true", // set this to true as we dont expect old email codes to be valid anymore @@ -95,10 +74,7 @@ export function checkEmailVerification( } if (organization || session.factors?.user?.organizationId) { - params.append( - "organization", - organization ?? (session.factors?.user?.organizationId as string), - ); + params.append("organization", organization ?? (session.factors?.user?.organizationId as string)); } return { redirect: `/verify?` + params }; @@ -113,15 +89,23 @@ export async function checkMFAFactors( organization?: string, requestId?: string, ) { + console.log("checkMFAFactors called with session:", { + sessionId: session.id, + userId: session.factors?.user?.id, + loginName: session.factors?.user?.loginName, + hasIntentFactor: !!session.factors?.intent?.verifiedAt, + hasPasswordFactor: !!session.factors?.password?.verifiedAt, + hasWebAuthNFactor: !!session.factors?.webAuthN?.verifiedAt, + }); const availableMultiFactors = authMethods?.filter( (m: AuthenticationMethodType) => - m !== AuthenticationMethodType.PASSWORD && - m !== AuthenticationMethodType.PASSKEY, + m === AuthenticationMethodType.TOTP || + m === AuthenticationMethodType.OTP_SMS || + m === AuthenticationMethodType.OTP_EMAIL || + m === AuthenticationMethodType.U2F, ); - const hasAuthenticatedWithPasskey = - session.factors?.webAuthN?.verifiedAt && - session.factors?.webAuthN?.userVerified; + const hasAuthenticatedWithPasskey = session.factors?.webAuthN?.verifiedAt && session.factors?.webAuthN?.userVerified; // escape further checks if user has authenticated with passkey if (hasAuthenticatedWithPasskey) { @@ -139,10 +123,7 @@ export async function checkMFAFactors( } if (organization || session.factors?.user?.organizationId) { - params.append( - "organization", - organization ?? (session.factors?.user?.organizationId as string), - ); + params.append("organization", organization ?? (session.factors?.user?.organizationId as string)); } const factor = availableMultiFactors[0]; @@ -166,59 +147,50 @@ export async function checkMFAFactors( } if (organization || session.factors?.user?.organizationId) { - params.append( - "organization", - organization ?? (session.factors?.user?.organizationId as string), - ); + params.append("organization", organization ?? (session.factors?.user?.organizationId as string)); } return { redirect: `/mfa?` + params }; - } else if ( - (loginSettings?.forceMfa || loginSettings?.forceMfaLocalOnly) && - !availableMultiFactors.length - ) { + } else if (shouldEnforceMFA(session, loginSettings) && !availableMultiFactors.length) { const params = new URLSearchParams({ loginName: session.factors?.user?.loginName as string, force: "true", // this defines if the mfa is forced in the settings checkAfter: "true", // this defines if the check is directly made after the setup }); + if (session.id) { + params.append("sessionId", session.id); + } + if (requestId) { params.append("requestId", requestId); } if (organization || session.factors?.user?.organizationId) { - params.append( - "organization", - organization ?? (session.factors?.user?.organizationId as string), - ); + params.append("organization", organization ?? (session.factors?.user?.organizationId as string)); } // TODO: provide a way to setup passkeys on mfa page? return { redirect: `/mfa/set?` + params }; } else if ( loginSettings?.mfaInitSkipLifetime && - (loginSettings.mfaInitSkipLifetime.nanos > 0 || - loginSettings.mfaInitSkipLifetime.seconds > 0) && + (loginSettings.mfaInitSkipLifetime.nanos > 0 || loginSettings.mfaInitSkipLifetime.seconds > 0) && !availableMultiFactors.length && - session?.factors?.user?.id + session?.factors?.user?.id && + shouldEnforceMFA(session, loginSettings) ) { const userResponse = await getUserByID({ serviceUrl, userId: session.factors?.user?.id, }); - const humanUser = - userResponse?.user?.type.case === "human" - ? userResponse?.user.type.value - : undefined; + const humanUser = userResponse?.user?.type.case === "human" ? userResponse?.user.type.value : undefined; if (humanUser?.mfaInitSkipped) { const mfaInitSkippedTimestamp = timestampDate(humanUser.mfaInitSkipped); const mfaInitSkipLifetimeMillis = - Number(loginSettings.mfaInitSkipLifetime.seconds) * 1000 + - loginSettings.mfaInitSkipLifetime.nanos / 1000000; + Number(loginSettings.mfaInitSkipLifetime.seconds) * 1000 + loginSettings.mfaInitSkipLifetime.nanos / 1000000; const currentTime = Date.now(); const mfaInitSkippedTime = mfaInitSkippedTimestamp.getTime(); const timeDifference = currentTime - mfaInitSkippedTime; @@ -237,15 +209,16 @@ export async function checkMFAFactors( checkAfter: "true", // this defines if the check is directly made after the setup }); + if (session.id) { + params.append("sessionId", session.id); + } + if (requestId) { params.append("requestId", requestId); } if (organization || session.factors?.user?.organizationId) { - params.append( - "organization", - organization ?? (session.factors?.user?.organizationId as string), - ); + params.append("organization", organization ?? (session.factors?.user?.organizationId as string)); } // TODO: provide a way to setup passkeys on mfa page? @@ -253,6 +226,52 @@ export async function checkMFAFactors( } } +/** + * Determines if MFA should be enforced based on the authentication method used and login settings + * @param session - The current session + * @param loginSettings - The login settings containing MFA enforcement rules + * @returns true if MFA should be enforced, false otherwise + */ +export function shouldEnforceMFA(session: Session, loginSettings: LoginSettings | undefined): boolean { + if (!loginSettings) { + return false; + } + + // Check if user authenticated with passkey (passkeys are inherently multi-factor) + const authenticatedWithPasskey = session.factors?.webAuthN?.verifiedAt && session.factors?.webAuthN?.userVerified; + + // If user authenticated with passkey, MFA is not required regardless of settings + if (authenticatedWithPasskey) { + return false; + } + + // If forceMfa is enabled, MFA is required for ALL authentication methods (except passkeys) + if (loginSettings.forceMfa) { + return true; + } + + // If forceMfaLocalOnly is enabled, MFA is only required for local/password authentication + if (loginSettings.forceMfaLocalOnly) { + // Check if user authenticated with password (local authentication) + const authenticatedWithPassword = !!session.factors?.password?.verifiedAt; + + // Check if user authenticated with IDP (external authentication) + const authenticatedWithIDP = !!session.factors?.intent?.verifiedAt; + + // If user authenticated with IDP, MFA is not required for forceMfaLocalOnly + if (authenticatedWithIDP) { + return false; + } + + // If user authenticated with password, MFA is required for forceMfaLocalOnly + if (authenticatedWithPassword) { + return true; + } + } + + return false; +} + export async function checkUserVerification(userId: string): Promise { // check if a verification was done earlier const cookiesList = await cookies(); @@ -264,24 +283,17 @@ export async function checkUserVerification(userId: string): Promise { return false; } - const verificationCheck = crypto - .createHash("sha256") - .update(`${userId}:${fingerPrintCookie.value}`) - .digest("hex"); + const verificationCheck = crypto.createHash("sha256").update(`${userId}:${fingerPrintCookie.value}`).digest("hex"); const cookieValue = await cookiesList.get("verificationCheck")?.value; if (!cookieValue) { - console.warn( - "User verification check cookie not found. User verification check failed.", - ); + console.warn("User verification check cookie not found. User verification check failed."); return false; } if (cookieValue !== verificationCheck) { - console.warn( - `User verification check failed. Expected ${verificationCheck} but got ${cookieValue}`, - ); + console.warn(`User verification check failed. Expected ${verificationCheck} but got ${cookieValue}`); return false; } diff --git a/src/lib/zitadel.ts b/src/lib/zitadel.ts index 5e4583f8af..ae2a12770c 100644 --- a/src/lib/zitadel.ts +++ b/src/lib/zitadel.ts @@ -2,40 +2,19 @@ import { Client, create, Duration } from "@zitadel/client"; import { createServerTransport as libCreateServerTransport } from "@zitadel/client/node"; import { makeReqCtx } from "@zitadel/client/v2"; import { IdentityProviderService } from "@zitadel/proto/zitadel/idp/v2/idp_service_pb"; -import { - OrganizationSchema, - TextQueryMethod, -} from "@zitadel/proto/zitadel/object/v2/object_pb"; -import { - CreateCallbackRequest, - OIDCService, -} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; +import { OrganizationSchema, TextQueryMethod } from "@zitadel/proto/zitadel/object/v2/object_pb"; +import { CreateCallbackRequest, OIDCService } from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb"; import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb"; import { OrganizationService } from "@zitadel/proto/zitadel/org/v2/org_service_pb"; -import { - CreateResponseRequest, - SAMLService, -} from "@zitadel/proto/zitadel/saml/v2/saml_service_pb"; +import { CreateResponseRequest, SAMLService } from "@zitadel/proto/zitadel/saml/v2/saml_service_pb"; import { RequestChallenges } from "@zitadel/proto/zitadel/session/v2/challenge_pb"; -import { - Checks, - SessionService, -} from "@zitadel/proto/zitadel/session/v2/session_service_pb"; +import { Checks, SessionService } from "@zitadel/proto/zitadel/session/v2/session_service_pb"; import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb"; import { SettingsService } from "@zitadel/proto/zitadel/settings/v2/settings_service_pb"; import { SendEmailVerificationCodeSchema } from "@zitadel/proto/zitadel/user/v2/email_pb"; -import type { - FormData, - RedirectURLsJson, -} from "@zitadel/proto/zitadel/user/v2/idp_pb"; -import { - NotificationType, - SendPasswordResetLinkSchema, -} from "@zitadel/proto/zitadel/user/v2/password_pb"; -import { - SearchQuery, - SearchQuerySchema, -} from "@zitadel/proto/zitadel/user/v2/query_pb"; +import type { FormData, RedirectURLsJson } from "@zitadel/proto/zitadel/user/v2/idp_pb"; +import { NotificationType, SendPasswordResetLinkSchema } from "@zitadel/proto/zitadel/user/v2/password_pb"; +import { SearchQuery, SearchQuerySchema } from "@zitadel/proto/zitadel/user/v2/query_pb"; import { SendInviteCodeSchema } from "@zitadel/proto/zitadel/user/v2/user_pb"; import { AddHumanUserRequest, @@ -73,8 +52,7 @@ export async function getHostedLoginTranslation({ organization?: string; locale?: string; }) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getHostedLoginTranslation( @@ -99,15 +77,8 @@ export async function getHostedLoginTranslation({ return useCache ? cacheWrapper(callback) : callback; } -export async function getBrandingSettings({ - serviceUrl, - organization, -}: { - serviceUrl: string; - organization?: string; -}) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); +export async function getBrandingSettings({ serviceUrl, organization }: { serviceUrl: string; organization?: string }) { + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getBrandingSettings({ ctx: makeReqCtx(organization) }, {}) @@ -116,15 +87,8 @@ export async function getBrandingSettings({ return useCache ? cacheWrapper(callback) : callback; } -export async function getLoginSettings({ - serviceUrl, - organization, -}: { - serviceUrl: string; - organization?: string; -}) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); +export async function getLoginSettings({ serviceUrl, organization }: { serviceUrl: string; organization?: string }) { + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getLoginSettings({ ctx: makeReqCtx(organization) }, {}) @@ -133,30 +97,16 @@ export async function getLoginSettings({ return useCache ? cacheWrapper(callback) : callback; } -export async function getSecuritySettings({ - serviceUrl, -}: { - serviceUrl: string; -}) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); +export async function getSecuritySettings({ serviceUrl }: { serviceUrl: string }) { + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); - const callback = settingsService - .getSecuritySettings({}) - .then((resp) => (resp.settings ? resp.settings : undefined)); + const callback = settingsService.getSecuritySettings({}).then((resp) => (resp.settings ? resp.settings : undefined)); return useCache ? cacheWrapper(callback) : callback; } -export async function getLockoutSettings({ - serviceUrl, - orgId, -}: { - serviceUrl: string; - orgId?: string; -}) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); +export async function getLockoutSettings({ serviceUrl, orgId }: { serviceUrl: string; orgId?: string }) { + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getLockoutSettings({ ctx: makeReqCtx(orgId) }, {}) @@ -165,15 +115,8 @@ export async function getLockoutSettings({ return useCache ? cacheWrapper(callback) : callback; } -export async function getPasswordExpirySettings({ - serviceUrl, - orgId, -}: { - serviceUrl: string; - orgId?: string; -}) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); +export async function getPasswordExpirySettings({ serviceUrl, orgId }: { serviceUrl: string; orgId?: string }) { + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getPasswordExpirySettings({ ctx: makeReqCtx(orgId) }, {}) @@ -182,77 +125,34 @@ export async function getPasswordExpirySettings({ return useCache ? cacheWrapper(callback) : callback; } -export async function listIDPLinks({ - serviceUrl, - userId, -}: { - serviceUrl: string; - userId: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function listIDPLinks({ serviceUrl, userId }: { serviceUrl: string; userId: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.listIDPLinks({ userId }, {}); } -export async function addOTPEmail({ - serviceUrl, - userId, -}: { - serviceUrl: string; - userId: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function addOTPEmail({ serviceUrl, userId }: { serviceUrl: string; userId: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.addOTPEmail({ userId }, {}); } -export async function addOTPSMS({ - serviceUrl, - userId, -}: { - serviceUrl: string; - userId: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function addOTPSMS({ serviceUrl, userId }: { serviceUrl: string; userId: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.addOTPSMS({ userId }, {}); } -export async function registerTOTP({ - serviceUrl, - userId, -}: { - serviceUrl: string; - userId: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function registerTOTP({ serviceUrl, userId }: { serviceUrl: string; userId: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.registerTOTP({ userId }, {}); } -export async function getGeneralSettings({ - serviceUrl, -}: { - serviceUrl: string; -}) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); +export async function getGeneralSettings({ serviceUrl }: { serviceUrl: string }) { + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); - const callback = settingsService - .getGeneralSettings({}, {}) - .then((resp) => resp.supportedLanguages); + const callback = settingsService.getGeneralSettings({}, {}).then((resp) => resp.supportedLanguages); return useCache ? cacheWrapper(callback) : callback; } @@ -264,8 +164,7 @@ export async function getLegalAndSupportSettings({ serviceUrl: string; organization?: string; }) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getLegalAndSupportSettings({ ctx: makeReqCtx(organization) }, {}) @@ -281,8 +180,7 @@ export async function getPasswordComplexitySettings({ serviceUrl: string; organization?: string; }) { - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); const callback = settingsService .getPasswordComplexitySettings({ ctx: makeReqCtx(organization) }) @@ -300,8 +198,7 @@ export async function createSessionFromChecks({ checks: Checks; lifetime: Duration; }) { - const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + const sessionService: Client = await createServiceForHost(SessionService, serviceUrl); const userAgent = await getUserAgent(); @@ -322,8 +219,8 @@ export async function createSessionForUserIdAndIdpIntent({ }; lifetime: Duration; }) { - const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + console.log("Creating session for userId and IDP intent", { userId, idpIntent, lifetime }); + const sessionService: Client = await createServiceForHost(SessionService, serviceUrl); const userAgent = await getUserAgent(); @@ -357,8 +254,7 @@ export async function setSession({ checks?: Checks; lifetime: Duration; }) { - const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + const sessionService: Client = await createServiceForHost(SessionService, serviceUrl); return sessionService.setSession( { @@ -382,8 +278,7 @@ export async function getSession({ sessionId: string; sessionToken: string; }) { - const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + const sessionService: Client = await createServiceForHost(SessionService, serviceUrl); return sessionService.getSession({ sessionId, sessionToken }, {}); } @@ -397,8 +292,7 @@ export async function deleteSession({ sessionId: string; sessionToken: string; }) { - const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + const sessionService: Client = await createServiceForHost(SessionService, serviceUrl); return sessionService.deleteSession({ sessionId, sessionToken }, {}); } @@ -409,8 +303,7 @@ type ListSessionsCommand = { }; export async function listSessions({ serviceUrl, ids }: ListSessionsCommand) { - const sessionService: Client = - await createServiceForHost(SessionService, serviceUrl); + const sessionService: Client = await createServiceForHost(SessionService, serviceUrl); return sessionService.listSessions( { @@ -436,36 +329,21 @@ export type AddHumanUserData = { organization: string; }; -export async function addHumanUser({ - serviceUrl, - email, - firstName, - lastName, - password, - organization, -}: AddHumanUserData) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function addHumanUser({ serviceUrl, email, firstName, lastName, password, organization }: AddHumanUserData) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); - let addHumanUserRequest: AddHumanUserRequest = create( - AddHumanUserRequestSchema, - { - email: { - email, - verification: { - case: "isVerified", - value: false, - }, + let addHumanUserRequest: AddHumanUserRequest = create(AddHumanUserRequestSchema, { + email: { + email, + verification: { + case: "isVerified", + value: false, }, - username: email, - profile: { givenName: firstName, familyName: lastName }, - passwordType: password - ? { case: "password", value: { password } } - : undefined, }, - ); + username: email, + profile: { givenName: firstName, familyName: lastName }, + passwordType: password ? { case: "password", value: { password } } : undefined, + }); if (organization) { const organizationSchema = create(OrganizationSchema, { @@ -481,32 +359,14 @@ export async function addHumanUser({ return userService.addHumanUser(addHumanUserRequest); } -export async function addHuman({ - serviceUrl, - request, -}: { - serviceUrl: string; - request: AddHumanUserRequest; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function addHuman({ serviceUrl, request }: { serviceUrl: string; request: AddHumanUserRequest }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.addHumanUser(request); } -export async function updateHuman({ - serviceUrl, - request, -}: { - serviceUrl: string; - request: UpdateHumanUserRequest; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function updateHuman({ serviceUrl, request }: { serviceUrl: string; request: UpdateHumanUserRequest }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.updateHumanUser(request); } @@ -520,40 +380,19 @@ export async function verifyTOTPRegistration({ code: string; userId: string; }) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.verifyTOTPRegistration({ code, userId }, {}); } -export async function getUserByID({ - serviceUrl, - userId, -}: { - serviceUrl: string; - userId: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function getUserByID({ serviceUrl, userId }: { serviceUrl: string; userId: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.getUserByID({ userId }, {}); } -export async function humanMFAInitSkipped({ - serviceUrl, - userId, -}: { - serviceUrl: string; - userId: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function humanMFAInitSkipped({ serviceUrl, userId }: { serviceUrl: string; userId: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.humanMFAInitSkipped({ userId }, {}); } @@ -567,10 +406,7 @@ export async function verifyInviteCode({ userId: string; verificationCode: string; }) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.verifyInviteCode({ userId, verificationCode }, {}); } @@ -596,10 +432,7 @@ export async function sendEmailCode({ }, }); - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.sendEmailCode(medium, {}); } @@ -622,10 +455,7 @@ export async function createInviteCode({ urlTemplate, }; - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.createInviteCode( { @@ -648,14 +478,7 @@ export type ListUsersCommand = { organizationId?: string; }; -export async function listUsers({ - serviceUrl, - loginName, - userName, - phone, - email, - organizationId, -}: ListUsersCommand) { +export async function listUsers({ serviceUrl, loginName, userName, phone, email, organizationId }: ListUsersCommand) { const queries: SearchQuery[] = []; // either use loginName or userName, email, phone @@ -738,10 +561,7 @@ export async function listUsers({ ); } - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.listUsers({ queries }); } @@ -771,7 +591,7 @@ const LoginNameQuery = (searchValue: string) => case: "loginNameQuery", value: { loginName: searchValue, - method: TextQueryMethod.EQUALS, + method: TextQueryMethod.EQUALS_IGNORE_CASE, }, }, }); @@ -782,7 +602,7 @@ const EmailQuery = (searchValue: string) => case: "emailQuery", value: { emailAddress: searchValue, - method: TextQueryMethod.EQUALS, + method: TextQueryMethod.EQUALS_IGNORE_CASE, }, }, }); @@ -791,13 +611,7 @@ const EmailQuery = (searchValue: string) => * this is a dedicated search function to search for users from the loginname page * it searches users based on the loginName or userName and org suffix combination, and falls back to email and phone if no users are found * */ -export async function searchUsers({ - serviceUrl, - searchValue, - loginSettings, - organizationId, - suffix, -}: SearchUsersCommand) { +export async function searchUsers({ serviceUrl, searchValue, loginSettings, organizationId, suffix }: SearchUsersCommand) { const queries: SearchQuery[] = []; // if a suffix is provided, we search for the userName concatenated with the suffix @@ -823,10 +637,7 @@ export async function searchUsers({ ); } - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); const loginNameResult = await userService.listUsers({ queries }); @@ -843,10 +654,7 @@ export async function searchUsers({ } const emailAndPhoneQueries: SearchQuery[] = []; - if ( - loginSettings.disableLoginWithEmail && - loginSettings.disableLoginWithPhone - ) { + if (loginSettings.disableLoginWithEmail && loginSettings.disableLoginWithPhone) { return { error: "User not found in the system" }; } else if (loginSettings.disableLoginWithEmail && searchValue.length <= 20) { const phoneQuery = PhoneQuery(searchValue); @@ -879,7 +687,7 @@ export async function searchUsers({ } if (organizationId) { - queries.push( + emailAndPhoneQueries.push( create(SearchQuerySchema, { query: { case: "organizationIdQuery", @@ -910,13 +718,8 @@ export async function searchUsers({ return { error: "User not found in the system" }; } -export async function getDefaultOrg({ - serviceUrl, -}: { - serviceUrl: string; -}): Promise { - const orgService: Client = - await createServiceForHost(OrganizationService, serviceUrl); +export async function getDefaultOrg({ serviceUrl }: { serviceUrl: string }): Promise { + const orgService: Client = await createServiceForHost(OrganizationService, serviceUrl); return orgService .listOrganizations( @@ -935,15 +738,8 @@ export async function getDefaultOrg({ .then((resp) => (resp?.result && resp.result[0] ? resp.result[0] : null)); } -export async function getOrgsByDomain({ - serviceUrl, - domain, -}: { - serviceUrl: string; - domain: string; -}) { - const orgService: Client = - await createServiceForHost(OrganizationService, serviceUrl); +export async function getOrgsByDomain({ serviceUrl, domain }: { serviceUrl: string; domain: string }) { + const orgService: Client = await createServiceForHost(OrganizationService, serviceUrl); return orgService.listOrganizations( { @@ -969,10 +765,7 @@ export async function startIdentityProviderFlow({ idpId: string; urls: RedirectURLsJson; }): Promise { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService .startIdentityProviderIntent({ @@ -999,10 +792,7 @@ export async function startIdentityProviderFlow({ }); const stringifiedFields = JSON.stringify(formData.fields); - console.log( - "Successfully stringified formData.fields, length:", - stringifiedFields.length, - ); + console.log("Successfully stringified formData.fields, length:", stringifiedFields.length); // Check cookie size limits (typical limit is 4KB) if (stringifiedFields.length > 4000) { @@ -1038,10 +828,7 @@ export async function startLDAPIdentityProviderFlow({ username: string; password: string; }) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.startIdentityProviderIntent({ idpId, @@ -1055,13 +842,7 @@ export async function startLDAPIdentityProviderFlow({ }); } -export async function getAuthRequest({ - serviceUrl, - authRequestId, -}: { - serviceUrl: string; - authRequestId: string; -}) { +export async function getAuthRequest({ serviceUrl, authRequestId }: { serviceUrl: string; authRequestId: string }) { const oidcService = await createServiceForHost(OIDCService, serviceUrl); return oidcService.getAuthRequest({ @@ -1069,13 +850,7 @@ export async function getAuthRequest({ }); } -export async function getDeviceAuthorizationRequest({ - serviceUrl, - userCode, -}: { - serviceUrl: string; - userCode: string; -}) { +export async function getDeviceAuthorizationRequest({ serviceUrl, userCode }: { serviceUrl: string; userCode: string }) { const oidcService = await createServiceForHost(OIDCService, serviceUrl); return oidcService.getDeviceAuthorizationRequest({ @@ -1108,25 +883,13 @@ export async function authorizeOrDenyDeviceAuthorization({ }); } -export async function createCallback({ - serviceUrl, - req, -}: { - serviceUrl: string; - req: CreateCallbackRequest; -}) { +export async function createCallback({ serviceUrl, req }: { serviceUrl: string; req: CreateCallbackRequest }) { const oidcService = await createServiceForHost(OIDCService, serviceUrl); return oidcService.createCallback(req); } -export async function getSAMLRequest({ - serviceUrl, - samlRequestId, -}: { - serviceUrl: string; - samlRequestId: string; -}) { +export async function getSAMLRequest({ serviceUrl, samlRequestId }: { serviceUrl: string; samlRequestId: string }) { const samlService = await createServiceForHost(SAMLService, serviceUrl); return samlService.getSAMLRequest({ @@ -1134,13 +897,7 @@ export async function getSAMLRequest({ }); } -export async function createResponse({ - serviceUrl, - req, -}: { - serviceUrl: string; - req: CreateResponseRequest; -}) { +export async function createResponse({ serviceUrl, req }: { serviceUrl: string; req: CreateResponseRequest }) { const samlService = await createServiceForHost(SAMLService, serviceUrl); return samlService.createResponse(req); @@ -1155,10 +912,7 @@ export async function verifyEmail({ userId: string; verificationCode: string; }) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.verifyEmail( { @@ -1188,43 +942,19 @@ export async function resendEmailCode({ request = { ...request, verification: { case: "sendCode", value: medium } }; - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.resendEmailCode(request, {}); } -export async function retrieveIDPIntent({ - serviceUrl, - id, - token, -}: { - serviceUrl: string; - id: string; - token: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function retrieveIDPIntent({ serviceUrl, id, token }: { serviceUrl: string; id: string; token: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); - return userService.retrieveIdentityProviderIntent( - { idpIntentId: id, idpIntentToken: token }, - {}, - ); + return userService.retrieveIdentityProviderIntent({ idpIntentId: id, idpIntentToken: token }, {}); } -export async function getIDPByID({ - serviceUrl, - id, -}: { - serviceUrl: string; - id: string; -}) { - const idpService: Client = - await createServiceForHost(IdentityProviderService, serviceUrl); +export async function getIDPByID({ serviceUrl, id }: { serviceUrl: string; id: string }) { + const idpService: Client = await createServiceForHost(IdentityProviderService, serviceUrl); return idpService.getIDPByID({ id }, {}).then((resp) => resp.idp); } @@ -1238,10 +968,7 @@ export async function addIDPLink({ idp: { id: string; userId: string; userName: string }; userId: string; }) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.addIDPLink( { @@ -1274,10 +1001,7 @@ export async function passwordReset({ urlTemplate, }; - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.passwordReset( { @@ -1319,10 +1043,7 @@ export async function setUserPassword({ }; } - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.setPassword(payload, {}).catch((error) => { // throw error if failed precondition (ex. User is not yet initialized) @@ -1334,17 +1055,8 @@ export async function setUserPassword({ }); } -export async function setPassword({ - serviceUrl, - payload, -}: { - serviceUrl: string; - payload: SetPasswordRequest; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function setPassword({ serviceUrl, payload }: { serviceUrl: string; payload: SetPasswordRequest }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.setPassword(payload, {}); } @@ -1355,17 +1067,8 @@ export async function setPassword({ * @param userId the id of the user where the email should be set * @returns the newly set email */ -export async function createPasskeyRegistrationLink({ - serviceUrl, - userId, -}: { - serviceUrl: string; - userId: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function createPasskeyRegistrationLink({ serviceUrl, userId }: { serviceUrl: string; userId: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.createPasskeyRegistrationLink({ userId, @@ -1383,19 +1086,8 @@ export async function createPasskeyRegistrationLink({ * @param domain the domain on which the factor is registered * @returns the newly set email */ -export async function registerU2F({ - serviceUrl, - userId, - domain, -}: { - serviceUrl: string; - userId: string; - domain: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function registerU2F({ serviceUrl, userId, domain }: { serviceUrl: string; userId: string; domain: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.registerU2F({ userId, @@ -1416,10 +1108,7 @@ export async function verifyU2FRegistration({ serviceUrl: string; request: VerifyU2FRegistrationRequest; }) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.verifyU2FRegistration(request, {}); } @@ -1444,8 +1133,7 @@ export async function getActiveIdentityProviders({ if (linking_allowed) { props.linkingAllowed = linking_allowed; } - const settingsService: Client = - await createServiceForHost(SettingsService, serviceUrl); + const settingsService: Client = await createServiceForHost(SettingsService, serviceUrl); return settingsService.getActiveIdentityProviders(props, {}); } @@ -1463,10 +1151,7 @@ export async function verifyPasskeyRegistration({ serviceUrl: string; request: VerifyPasskeyRegistrationRequest; }) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.verifyPasskeyRegistration(request, {}); } @@ -1490,10 +1175,7 @@ export async function registerPasskey({ code: { id: string; code: string }; domain: string; }) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.registerPasskey({ userId, @@ -1508,17 +1190,8 @@ export async function registerPasskey({ * @param userId the id of the user where the email should be set * @returns the list of authentication method types */ -export async function listAuthenticationMethodTypes({ - serviceUrl, - userId, -}: { - serviceUrl: string; - userId: string; -}) { - const userService: Client = await createServiceForHost( - UserService, - serviceUrl, - ); +export async function listAuthenticationMethodTypes({ serviceUrl, userId }: { serviceUrl: string; userId: string }) { + const userService: Client = await createServiceForHost(UserService, serviceUrl); return userService.listAuthenticationMethodTypes({ userId, @@ -1533,16 +1206,14 @@ export function createServerTransport(token: string, baseUrl: string) { : [ (next) => { return (req) => { - process.env - .CUSTOM_REQUEST_HEADERS!.split(",") - .forEach((header) => { - const kv = header.split(":"); - if (kv.length === 2) { - req.header.set(kv[0].trim(), kv[1].trim()); - } else { - console.warn(`Skipping malformed header: ${header}`); - } - }); + process.env.CUSTOM_REQUEST_HEADERS!.split(",").forEach((header) => { + const kv = header.split(":"); + if (kv.length === 2) { + req.header.set(kv[0].trim(), kv[1].trim()); + } else { + console.warn(`Skipping malformed header: ${header}`); + } + }); return next(req); }; }, diff --git a/tsconfig.json b/tsconfig.json index 4cec65b3dc..dbb65389d4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -19,7 +23,9 @@ } ], "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], diff --git a/turbo.json b/turbo.json deleted file mode 100644 index dce47a5a3a..0000000000 --- a/turbo.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "extends": [ - "//" - ], - "tasks": { - "build": { - "outputs": [ - "dist/**", - ".next/**", - "!.next/cache/**" - ], - "dependsOn": [ - "@zitadel/client#build" - ] - }, - "build:login:standalone": { - "outputs": [ - "dist/**", - ".next/**", - "!.next/cache/**" - ], - "dependsOn": [ - "@zitadel/client#build" - ] - }, - "dev": { - "persistent": true, - "cache": false, - "dependsOn": [ - "@zitadel/client#build" - ] - }, - "test": { - "dependsOn": [ - "@zitadel/client#build" - ] - }, - "test:unit": { - "dependsOn": [ - "@zitadel/client#build" - ] - }, - "test:integration:login": { - "inputs": [ - ".next/**", - "!.next/cache/**", - "integration/integration/**", - "integration/support/**", - "cypress.config.ts" - ], - "outputs": [ - "cypress/videos/**", - "cypress/screenshots/**" - ] - } - } -} \ No newline at end of file diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000000..884322f987 --- /dev/null +++ b/vercel.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "pnpm nx run @zitadel/login:build-vercel" +} \ No newline at end of file