diff --git a/.github/workflows/auth-react-test-1.yml b/.github/workflows/auth-react-test-1.yml new file mode 100644 index 000000000..d35eb4690 --- /dev/null +++ b/.github/workflows/auth-react-test-1.yml @@ -0,0 +1,83 @@ +name: Auth-React Tests - L1 + +on: + pull_request: + types: + - opened + - reopened + - synchronize + push: + tags: + - dev-v[0-9]+.[0-9]+.[0-9]+ + +# Only one instance of this workflow will run on the same ref (PR/Branch/Tag) +# Previous runs will be cancelled. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + define-versions: + runs-on: ubuntu-latest + outputs: + fdiVersions: ${{ steps.versions.outputs.fdiVersions }} + steps: + - uses: actions/checkout@v4 + - uses: supertokens/get-supported-versions-action@main + id: versions + with: + has-fdi: true + + setup-auth-react: + runs-on: ubuntu-latest + needs: define-versions + strategy: + fail-fast: true + matrix: + fdi-version: ${{ fromJSON(needs.define-versions.outputs.fdiVersions) }} + + outputs: + AUTH_REACT__LOG_DIR: ${{ steps.envs.outputs.AUTH_REACT__LOG_DIR }} + AUTH_REACT__SCREENSHOT_DIR: ${{ steps.envs.outputs.AUTH_REACT__SCREENSHOT_DIR }} + AUTH_REACT__NODE_PORT: ${{ steps.envs.outputs.AUTH_REACT__NODE_PORT }} + AUTH_REACT__APP_SERVER: ${{ steps.envs.outputs.AUTH_REACT__NODE_PORT }} + AUTH_REACT__TEST_MODE: ${{ steps.envs.outputs.AUTH_REACT__TEST_MODE }} + AUTH_REACT__PORT: ${{ steps.envs.outputs.AUTH_REACT__PORT }} + specs: ${{ steps.envs.outputs.specs }} + fdiVersions: ${{ needs.define-versions.outputs.fdiVersions }} + + steps: + - uses: supertokens/get-versions-action@main + id: versions + with: + driver-name: node + fdi-version: ${{ matrix.fdi-version }} + env: + SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }} + + - uses: supertokens/auth-react-testing-action/setup@main + id: envs + with: + auth-react-version: ${{ github.sha }} + node-sdk-version: ${{ steps.versions.outputs.nodeTag }} + fdi-version: ${{ matrix.fdi-version }} + use-common-app-and-test-server: "true" + + launch-test-workflow: + uses: ./.github/workflows/auth-react-test-2.yml + needs: setup-auth-react + name: FDI ${{ matrix.fdi-version }} + strategy: + max-parallel: 1 # This is important to avoid ddos GHA API + fail-fast: false # Don't fail fast to avoid locking TF State + matrix: + fdi-version: ${{ fromJSON(needs.setup-auth-react.outputs.fdiVersions) }} + with: + fdi-version: ${{ matrix.fdi-version }} + specs: ${{ needs.setup-auth-react.outputs.specs }} + AUTH_REACT__LOG_DIR: ${{ needs.setup-auth-react.outputs.AUTH_REACT__LOG_DIR }} + AUTH_REACT__SCREENSHOT_DIR: ${{ needs.setup-auth-react.outputs.AUTH_REACT__SCREENSHOT_DIR }} + AUTH_REACT__APP_SERVER: ${{ needs.setup-auth-react.outputs.AUTH_REACT__APP_SERVER }} + AUTH_REACT__NODE_PORT: ${{ needs.setup-auth-react.outputs.AUTH_REACT__NODE_PORT }} + AUTH_REACT__TEST_MODE: ${{ needs.setup-auth-react.outputs.AUTH_REACT__TEST_MODE }} + AUTH_REACT__PORT: ${{ needs.setup-auth-react.outputs.AUTH_REACT__PORT }} diff --git a/.github/workflows/auth-react-test-2.yml b/.github/workflows/auth-react-test-2.yml new file mode 100644 index 000000000..37b8c903b --- /dev/null +++ b/.github/workflows/auth-react-test-2.yml @@ -0,0 +1,82 @@ +name: Auth-React Tests - L2 +on: + workflow_call: + inputs: + fdi-version: + description: "FDI Version" + required: true + type: string + + specs: + description: "Spec Files" + required: true + type: string + + AUTH_REACT__LOG_DIR: + description: AUTH_REACT__LOG_DIR + required: true + type: string + + AUTH_REACT__SCREENSHOT_DIR: + description: AUTH_REACT__SCREENSHOT_DIR + required: true + type: string + + AUTH_REACT__APP_SERVER: + description: AUTH_REACT__APP_SERVER + required: true + type: string + + AUTH_REACT__NODE_PORT: + description: AUTH_REACT__NODE_PORT + required: true + type: string + + AUTH_REACT__TEST_MODE: + description: AUTH_REACT__TEST_MODE + required: true + type: string + + AUTH_REACT__PORT: + description: AUTH_REACT__PORT + required: true + type: string + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + max-parallel: 10 + fail-fast: false + matrix: + spec: ${{ fromJSON(inputs.specs) }} + + env: + SUPERTOKENS_CORE_PORT: 3567 + SUPERTOKENS_CORE_HOST: localhost + TEST_MODE: testing + # Auth react setup envs + AUTH_REACT__LOG_DIR: ${{ inputs.AUTH_REACT__LOG_DIR }} + AUTH_REACT__SCREENSHOT_DIR: ${{ inputs.AUTH_REACT__SCREENSHOT_DIR }} + AUTH_REACT__NODE_PORT: ${{ inputs.AUTH_REACT__NODE_PORT }} + AUTH_REACT__APP_SERVER: ${{ inputs.AUTH_REACT__NODE_PORT }} + AUTH_REACT__TEST_MODE: ${{ inputs.AUTH_REACT__TEST_MODE }} + AUTH_REACT__PORT: ${{ inputs.AUTH_REACT__PORT }} + + steps: + - uses: actions/checkout@v4 + with: + path: supertokens-auth-react + + - name: Start core + run: docker compose up --wait + working-directory: supertokens-auth-react + + - uses: supertokens/auth-react-testing-action@main + name: test ${{ matrix.spec }} for ${{ inputs.fdi-version }} + with: + fdi-version: ${{ inputs.fdi-version }} + check-name-suffix: "[FDI=${{ inputs.fdi-version }}][Spec=${{ matrix.spec }}]" + path: supertokens-auth-react + spec: ${{ matrix.spec }} diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 7dd1d3a16..eaf70f4af 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -2,9 +2,19 @@ name: "Chromatic" on: push: - branches: [master, "[0-9]*.[0-9]*"] + branches: + - master + - "[0-9]*.[0-9]*" + tags: + - dev-v[0-9]+.[0-9]+.[0-9]+ workflow_dispatch: +# Only one instance of this workflow will run on the same ref (PR/Branch/Tag) +# Previous runs will be cancelled. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: chromatic: runs-on: ubuntu-latest diff --git a/.github/workflows/pipeline-dev-tag.yml b/.github/workflows/pipeline-dev-tag.yml new file mode 100644 index 000000000..6f5be1af0 --- /dev/null +++ b/.github/workflows/pipeline-dev-tag.yml @@ -0,0 +1,153 @@ +name: "Dev Tag Pipeline" + +on: + workflow_dispatch: + inputs: + branch: + description: The branch to create the dev tag on + type: string + required: true + +permissions: + contents: write + +jobs: + setup: + runs-on: ubuntu-latest + + outputs: + packageVersion: ${{ steps.versions.outputs.packageVersion }} + packageVersionXy: ${{ steps.versions.outputs.packageVersionXy }} + packageLockVersion: ${{ steps.versions.outputs.packageLockVersion }} + packageLockVersionXy: ${{ steps.versions.outputs.packageLockVersionXy }} + newestVersion: ${{ steps.versions.outputs.newestVersion }} + targetBranch: ${{ steps.versions.outputs.targetBranch }} + devTag: ${{ steps.versions.outputs.devTag }} + releaseTag: ${{ steps.versions.outputs.releaseTag }} + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + # Need a complete fetch to make the master merge check work + fetch-depth: 0 + fetch-tags: true + token: ${{ secrets.ALL_REPO_PAT }} + + - name: Setup git + run: | + # NOTE: The user email is {user.id}+{user.login}@users.noreply.github.com. + # See users API: https://api.github.com/users/github-actions%5Bbot%5D + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git fetch origin master + + - name: Check if branch needs master merge + run: | + if [[ $(git log origin/master ^HEAD) != "" ]]; then + echo "You need to merge master into this branch." + exit 1 + fi + + - name: Populate variables + id: versions + run: | + . ./hooks/populate-hook-constants.sh + + echo "packageVersion=$packageVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "packageVersionXy=$packageVersionXy" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "packageLockVersion=$packageLockVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "packageLockVersionXy=$packageLockVersionXy" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "newestVersion=$newestVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "targetBranch=$targetBranch" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + + echo "devTag=dev-v$packageLockVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "releaseTag=v$packageLockVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + + - name: Check tag and branch correctness + run: | + if [[ "${{ steps.versions.outputs.packageVersion }}" != "${{ steps.versions.outputs.packageLockVersion }}" ]] + then + echo "The package version and package lock version do not match." + exit 1 + fi + + if [[ "${{ steps.versions.outputs.packageVersion }}" != ${{ inputs.branch }}* ]] + then + echo "Adding tag to wrong branch" + exit 1 + fi + + if git rev-parse ${{ steps.versions.outputs.releaseTag }} >/dev/null 2>&1 + then + echo "The released version of this tag already exists." + exit 1 + fi + + - name: Delete tag if already tagged + run: | + git tag --delete ${{ steps.versions.outputs.devTag }} || true + git push --delete origin ${{ steps.versions.outputs.devTag }} || true + + - name: Install dependencies + run: npm install + + - name: Build docs + run: | + npm run build-pretty + npm run build-docs + + - name: Commit doc changes + run: | + git add --all + git commit --allow-empty -nm "doc: update docs for ${{ steps.versions.outputs.releaseTag }} tag" + git push + + - name: Create and push tag + run: | + # NOTE: The user email is {user.id}+{user.login}@users.noreply.github.com. + # See users API: https://api.github.com/users/github-actions%5Bbot%5D + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git tag ${{ steps.versions.outputs.devTag }} + git push --tags --follow-tags + + mark-dev-tag-as-not-passed: + runs-on: ubuntu-latest + needs: + - setup + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.setup.outputs.devTag }} + fetch-tags: true + + - id: versions + uses: supertokens/get-supported-versions-action@main + with: + has-cdi: false + has-fdi: true + has-web-js: true + + - id: escape-versions + run: | + echo "fdiVersions=$(sed 's/"/\\"/g' <<< '${{ steps.versions.outputs.fdiVersions }}')" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "webJsInterfaceVersion=$(sed 's/"/\\"/g' <<< '${{ steps.versions.outputs.webJsInterfaceVersion }}')" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + + - run: | + ./hooks/populate-hook-constants.sh + + curl --fail-with-body -X PUT \ + https://api.supertokens.io/0/frontend \ + -H 'Content-Type: application/json' \ + -H 'api-version: 1' \ + -d "{ + \"password\": \"${{ secrets.SUPERTOKENS_API_KEY }}\", + \"version\":\"${{ needs.setup.outputs.packageVersion }}\", + \"name\": \"auth-react\", + \"frontendDriverInterfaces\": ${{ steps.escape-versions.outputs.fdiVersions }}, + \"webJsInterface\": \"${{ steps.escape-versions.outputs.webJsInterfaceVersion }}\" + }" diff --git a/.github/workflows/pipeline-release-tag.yml b/.github/workflows/pipeline-release-tag.yml new file mode 100644 index 000000000..b6d23d05f --- /dev/null +++ b/.github/workflows/pipeline-release-tag.yml @@ -0,0 +1,383 @@ +name: "Release Pipeline" + +on: + workflow_dispatch: + inputs: + branch: + description: The branch to create the release tag on + type: string + required: true + + skip-test-checks: + description: Skip tests passed checks + type: boolean + default: false + required: false + + skip-other-version-checks: + description: Skip server checks for core and frontend versions + type: boolean + default: false + required: false + +permissions: + contents: write + +jobs: + setup: + runs-on: ubuntu-latest + + outputs: + packageVersion: ${{ steps.versions.outputs.packageVersion }} + packageVersionXy: ${{ steps.versions.outputs.packageVersionXy }} + packageLockVersion: ${{ steps.versions.outputs.packageLockVersion }} + packageLockVersionXy: ${{ steps.versions.outputs.packageLockVersionXy }} + newestVersion: ${{ steps.versions.outputs.newestVersion }} + targetBranch: ${{ steps.versions.outputs.targetBranch }} + devTag: ${{ steps.versions.outputs.devTag }} + releaseTag: ${{ steps.versions.outputs.releaseTag }} + targetFolder: ${{ steps.versions.outputs.targetFolder }} + versionFolder: ${{ steps.versions.outputs.versionFolder }} + artifactName: ${{ steps.versions.outputs.artifactName }} + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + fetch-tags: true + token: ${{ secrets.ALL_REPO_PAT }} + + - name: Populate variables + id: versions + run: | + . ./hooks/populate-hook-constants.sh + + echo "packageVersion=$packageVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "packageVersionXy=$packageVersionXy" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "packageLockVersion=$packageLockVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "packageLockVersionXy=$packageLockVersionXy" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "newestVersion=$newestVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "targetBranch=$targetBranch" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + + echo "devTag=dev-v$packageLockVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "releaseTag=v$packageLockVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + + echo "targetFolder=app/docs/sdk/docs/auth-react" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "versionFolder=$packageVersionXy.X" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + echo "artifactName=auth-react-docs-$packageVersion" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + + mark-as-success: + runs-on: ubuntu-latest + + needs: + - setup + + steps: + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + + - name: Install dependencies + run: | + pip install httpx + + - if: ${{ inputs.skip-test-checks == 'false' || inputs.skip-test-checks == false }} + name: Get commit status + run: | + python3 -c "$(cat << EOF + + from collections import defaultdict + import httpx + import sys + + check_runs_url = "https://api.github.com/repos/${{ github.repository }}/commits/tags/${{ needs.setup.outputs.devTag }}/check-runs?per_page=100&page={page}" + jobs_url="https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs" + + current_jobs_response = httpx.get(jobs_url).json() + current_job_ids = [job["id"] for job in current_jobs_response["jobs"]] + + page = 1 + total = 0 + + status_map = defaultdict(int) + conclusion_map = defaultdict(int) + failures = [] + + while True: + response = httpx.get(check_runs_url.format(page=page)).json() + + if len(response["check_runs"]) == 0: + break + + for run_info in response["check_runs"]: + # Release pipeline jobs also show up in check-runs + # We skip them from the checks to avoid pipeline failures + if run_info["id"] in current_job_ids: + continue + + if run_info["conclusion"] == "failure": + failures.append(run_info["html_url"]) + + status_map[run_info["status"]] += 1 + conclusion_map[run_info["conclusion"]] += 1 + total += 1 + + page += 1 + + print(f"{page=}") + print(f"{total=}") + print("Status Map =", dict(status_map)) + print("Conclusion Map =", dict(conclusion_map)) + print() + + # Possible values (from docs): + # [completed, action_required, cancelled, failure, neutral, skipped, stale, success, + # timed_out, in_progress, queued, requested, waiting, pending] + if status_map["completed"] < total: + print("Some checks not completed.") + print(failures) + sys.exit(1) + + # Possible values (from testing): + # None, success, skipped, failure + if conclusion_map.get("failure", 0) > 0: + print("Some checks not successful.") + print(failures) + sys.exit(1) + + EOF + )" + + - run: | + curl --fail-with-body -X PATCH \ + https://api.supertokens.io/0/frontend \ + -H 'Content-Type: application/json' \ + -H 'api-version: 0' \ + -d "{ + \"password\": \"${{ secrets.SUPERTOKENS_API_KEY }}\", + \"version\":\"${{ needs.setup.outputs.packageVersion }}\", + \"name\": \"auth-react\", + \"testPassed\": true + }" + + release: + runs-on: ubuntu-latest + + # This job marks the version as a release version and creates tags. + # Binding this to the publish env to require approvals before run. + # Further jobs are follow-ups to the release and are not required to be approved once release is approved. + environment: publish + + needs: + - setup + - mark-as-success + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + fetch-tags: true + token: ${{ secrets.ALL_REPO_PAT }} + + - name: Setup git + run: | + # NOTE: The user email is {user.id}+{user.login}@users.noreply.github.com. + # See users API: https://api.github.com/users/github-actions%5Bbot%5D + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Check tests passed + run: | + testsPassed=`curl -s -X GET "https://api.supertokens.io/0/frontend?password=${{ secrets.SUPERTOKENS_API_KEY }}&version=${{ needs.setup.outputs.packageVersion }}&name=auth-react" -H 'api-version: 0'` + + if [[ $(echo $testsPassed | jq .testPassed) != "true" ]] + then + echo "All tests have not passed. Exiting." + exit 1 + fi + + # - if: ${{ inputs.skip-other-version-checks == 'false' || inputs.skip-other-version-checks == false }} + # name: Check if core and frontend released + # run: | + # canReleaseSafelyResponse=`curl -s -X GET "https://api.supertokens.io/0/frontend/release/check?password=${{ secrets.SUPERTOKENS_API_KEY }}&version=${{ needs.setup.outputs.packageVersion }}&name=auth-react" -H 'api-version: 0'` + + # if [[ $(echo $canReleaseSafelyResponse | jq .canRelease) != "true" ]] + # then + # echo "Cannot release. Have you released corresponding core and frontend?" + # exit 1 + # fi + + - name: Check if current commit is dev-tagged + run: | + currentCommit=$(git log --format="%H" -n 1) + currentTag=`git tag -l --points-at $currentCommit` + expectedTag="${{ needs.setup.outputs.devTag }}" + + if [[ "$currentTag" != "$expectedTag" ]] + then + echo "Commit does not have the correct dev tag for this release" + echo "Current: $currentTag" + echo "Expected: $expectedTag" + exit 1 + fi + + - name: Mark for release + run: | + curl --fail-with-body -X PATCH \ + https://api.supertokens.io/0/frontend \ + -H 'Content-Type: application/json' \ + -H 'api-version: 0' \ + -d "{ + \"password\": \"${{ secrets.SUPERTOKENS_RELEASE_API_KEY }}\", + \"name\":\"auth-react\", + \"version\":\"${{ needs.setup.outputs.packageVersion }}\", + \"release\": true + }" + + - name: Create release tag, delete dev tag + run: | + # Add new release tag + git tag ${{ needs.setup.outputs.releaseTag }} + git push --tags + + # Delete current dev tag + git tag --delete ${{ needs.setup.outputs.devTag }} + git push --delete origin ${{ needs.setup.outputs.devTag }} + + merge: + runs-on: ubuntu-latest + needs: + - setup + - release + + outputs: + isLatest: ${{ steps.merge-check.outputs.isLatest }} + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.branch }} + # Need a complete fetch to make the master merge work + fetch-depth: 0 + fetch-tags: true + token: ${{ secrets.ALL_REPO_PAT }} + + - name: Setup git + run: | + # NOTE: The user email is {user.id}+{user.login}@users.noreply.github.com. + # See users API: https://api.github.com/users/github-actions%5Bbot%5D + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Check API and merge to master + id: merge-check + run: | + response=`curl -s -X GET "https://api.supertokens.io/0/frontend/latest/check?password=${{ secrets.SUPERTOKENS_API_KEY }}&version=${{ needs.setup.outputs.packageVersion }}&name=auth-react" -H 'api-version: 0'` + isLatest=$(echo $response | jq .isLatest) + + echo "isLatest=$isLatest" | tee -a "$GITHUB_OUTPUT" "$GITHUB_ENV" + + if [[ $isLatest == "true" ]] + then + git checkout master + git checkout ${{ inputs.branch }} + + git merge master + git checkout master + git merge ${{ inputs.branch }} + git push + git checkout ${{ inputs.branch }} + fi + + publish-docs: + runs-on: ubuntu-latest + needs: + - setup + - release + - merge + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.setup.outputs.releaseTag }} + fetch-tags: true + path: supertokens-auth-react + + - uses: actions/checkout@v4 + with: + repository: supertokens/supertokens-backend-website + token: ${{ secrets.ALL_REPO_PAT }} + path: supertokens-backend-website + + - run: | + shopt -s extglob # Enable extended globbing + + if [[ "${{ needs.merge.outputs.isLatest }}" == "true" ]] + then + # Delete everything except the version folders + rm -rf supertokens-backend-website/${{ needs.setup.outputs.targetFolder }}/!(*.*.X) + # Copy files to the root dir + cp -r supertokens-auth-react/docs/* supertokens-backend-website/${{ needs.setup.outputs.targetFolder }}/ + fi + + # Delete the current version folder if it exists + rm -rf supertokens-backend-website/${{ needs.setup.outputs.targetFolder }}/${{ needs.setup.outputs.versionFolder }} + # Copy the current docs + mkdir -p supertokens-backend-website/${{ needs.setup.outputs.targetFolder }}/${{ needs.setup.outputs.versionFolder }} + cp -r supertokens-auth-react/docs/* supertokens-backend-website/${{ needs.setup.outputs.targetFolder }}/${{ needs.setup.outputs.versionFolder }} + + - uses: actions/upload-artifact@v4 + with: + name: ${{ needs.setup.outputs.artifactName }} + path: supertokens-backend-website/${{ needs.setup.outputs.targetFolder }} + + - name: Trigger the backend website CI + uses: actions/github-script@v7 + with: + # NOTE: We should use a better scoped PAT here. + github-token: ${{ secrets.ALL_REPO_PAT }} + script: | + github.rest.actions.createWorkflowDispatch({ + owner: 'supertokens', + repo: 'supertokens-backend-website', + workflow_id: 'release-sdk-documentation-changes.yml', + ref: 'master', + inputs: { + "sdk-name": "auth-react", + "sdk-repo": "supertokens/supertokens-auth-react", + "version": `${{ needs.setup.outputs.packageVersion }}`, + "artifact-name": `${{ needs.setup.outputs.artifactName }}`, + "target-folder": `${{ needs.setup.outputs.targetFolder }}`, + "run-id": `${{ github.run_id }}`, + "stage": "production", + } + }) + + publish: + runs-on: ubuntu-latest + environment: publish + needs: + - setup + - release + - merge + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.setup.outputs.releaseTag }} + fetch-tags: true + + - uses: actions/setup-node@v4 + with: + node-version: "20" + registry-url: "https://registry.npmjs.org/" + + - run: | + if [[ "${{ needs.merge.outputs.isLatest }}" == "true" ]] + then + npm publish --tag latest + else + npm publish --tag version-${{ needs.setup.outputs.packageVersion }} + fi + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/pre-commit-hook-run.yml b/.github/workflows/pre-commit-hook-run.yml index 968977f92..71669f1b7 100644 --- a/.github/workflows/pre-commit-hook-run.yml +++ b/.github/workflows/pre-commit-hook-run.yml @@ -12,15 +12,8 @@ jobs: pr-title: name: Pre commit hook check runs-on: ubuntu-latest - container: rishabhpoddar/supertokens_website_sdk_testing_node_16 steps: - - uses: actions/checkout@v2 - - run: git init && git add --all && git -c user.name='test' -c user.email='test@example.com' commit -m 'init for pr action' - - run: npm i --force || true - # the below command is there cause otherwise running npm run check-circular-dependencies gives an error like: - # Your cache folder contains root-owned files, due to a bug in - # npm ERR! previous versions of npm which has since been addressed. - - run: chown -R 1001:121 "/github/home/.npm" - - run: npm i --force - - run: cd test/with-typescript && npm i --force + - uses: actions/checkout@v4 + - run: npm ci + - run: cd test/with-typescript && npm ci - run: ./hooks/pre-commit.sh diff --git a/.github/workflows/test-examples.yml b/.github/workflows/test-examples.yml index a799a33da..1a014bd23 100644 --- a/.github/workflows/test-examples.yml +++ b/.github/workflows/test-examples.yml @@ -1,12 +1,28 @@ name: "Test examples" -on: push + +on: + pull_request: + types: + - opened + - reopened + - synchronize + push: + tags: + - dev-v[0-9]+.[0-9]+.[0-9]+ + +# Only one instance of this workflow will run on the same ref (PR/Branch/Tag) +# Previous runs will be cancelled. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: setup: runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: bash test/findExamplesWithTests.sh - id: set-matrix run: echo "::set-output name=matrix::{\"include\":$(bash test/findExamplesWithTests.sh)}" @@ -24,7 +40,7 @@ jobs: run: working-directory: ${{ matrix.examplePath }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - run: bash ../../test/updateExampleAppDeps.sh . - run: npm install mocha@6.1.4 jsdom-global@3.0.2 puppeteer@^11.0.0 isomorphic-fetch@^3.0.0 - run: npm run build || true @@ -36,7 +52,7 @@ jobs: ) - name: The job has failed if: ${{ failure() }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: screenshots path: ./**/*screenshot.jpeg diff --git a/.github/workflows/tests-pass-check-pr.yml b/.github/workflows/tests-pass-check-pr.yml deleted file mode 100644 index cfa2cbaec..000000000 --- a/.github/workflows/tests-pass-check-pr.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: 'Check if "Run tests" action succeeded' - -on: - pull_request: - types: - - opened - - reopened - - edited - - synchronize - -jobs: - pr-run-test-action: - name: Check if "Run tests" action succeeded - timeout-minutes: 60 - concurrency: - group: ${{ github.head_ref }} - cancel-in-progress: true - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: node install - run: cd ./.github/helpers && npm i - - name: Calling github API - run: cd ./.github/helpers && GITHUB_TOKEN=${{ github.token }} REPO=${{ github.repository }} RUN_ID=${{ github.run_id }} BRANCH=${{ github.head_ref }} JOB_ID=${{ github.job }} SOURCE_OWNER=${{ github.event.pull_request.head.repo.owner.login }} CURRENT_SHA=${{ github.event.pull_request.head.sha }} node node_modules/github-workflow-helpers/test-pass-check-pr.js diff --git a/.github/workflows/tests-visual.yml b/.github/workflows/tests-visual.yml deleted file mode 100644 index 67c850a99..000000000 --- a/.github/workflows/tests-visual.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: "Run visual tests" -on: workflow_dispatch -jobs: - test_job: - name: Run visual tests - timeout-minutes: 120 - runs-on: ubuntu-latest - container: - image: rishabhpoddar/supertokens_website_sdk_testing_node_16 - options: "-u 1001" - env: - PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} - steps: - - uses: actions/checkout@v2 - with: - persist-credentials: false - - name: Make git use https instead of ssh - run: git config --global url."https://github.com/".insteadOf ssh://git@github.com/ - - run: npm run init - - name: Cloning supertokens-root - run: cd ../ && git clone https://github.com/supertokens/supertokens-root.git - - name: Modifying modules.txt in supertokens-root - run: cd ../supertokens-root && echo "core,master,supertokens\nplugin-interface,master,supertokens" > modules.txt - - name: Contents of modules.txt - run: cat ../supertokens-root/modules.txt - - name: Running loadModules in supertokens-root - run: cd ../supertokens-root && ./loadModules - - name: Installing supertokens-node - run: cd test/server/ && npm i -d --force && npm i --force github:supertokens/supertokens-node - - name: Setting up supertokens-root test environment - run: cd ../supertokens-root && bash ./utils/setupTestEnv --local - - name: Run tests - run: npm run test-visual diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 000000000..5b742cd75 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,28 @@ +name: Auth-React Unit Tests + +on: + pull_request: + types: + - opened + - reopened + - synchronize + push: + tags: + - dev-v[0-9]+.[0-9]+.[0-9]+ + +# Only one instance of this workflow will run on the same ref (PR/Branch/Tag) +# Previous runs will be cancelled. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - run: npm install + - run: npm run test-unit diff --git a/.mocharc.yml b/.mocharc.yml index f496f51ef..5d684c46d 100644 --- a/.mocharc.yml +++ b/.mocharc.yml @@ -1,6 +1,11 @@ -spec: - - test/unit/**/*.test.js - - test/end-to-end/**/*.test.js -reporter: spec +# spec: +# - test/end-to-end/**/*.test.js slow: 20000 -timeout: 30000 +timeout: 40000 +exit: true +require: + - "@babel/register" + - test/test.mocha.env +reporter: mocha-multi-reporters +reporter-option: + - configFile=mocha-multi-reporters.json diff --git a/CHANGELOG.md b/CHANGELOG.md index aacc5e850..0185a869a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +- Adds dev/release GHA pipelines + ## [0.49.1] - 2025-03-27 - Fixed a type issue making the WebauthnPreBuitlUI not produce a type error when added to the prebuiltUIList diff --git a/compose.yml b/compose.yml new file mode 100644 index 000000000..9d01cf43d --- /dev/null +++ b/compose.yml @@ -0,0 +1,23 @@ +services: + core: + # Uses `$SUPERTOKENS_CORE_VERSION` when available, else latest + image: supertokens/supertokens-core:dev-branch-${SUPERTOKENS_CORE_VERSION:-master} + ports: + # Uses `$SUPERTOKENS_CORE_PORT` when available, else 3567 for local port + - ${SUPERTOKENS_CORE_PORT:-3567}:3567 + platform: linux/amd64 + depends_on: [oauth] + environment: + OAUTH_PROVIDER_PUBLIC_SERVICE_URL: http://oauth:4444 + OAUTH_PROVIDER_ADMIN_SERVICE_URL: http://oauth:4445 + OAUTH_PROVIDER_CONSENT_LOGIN_BASE_URL: http://localhost:3001/auth + OAUTH_CLIENT_SECRET_ENCRYPTION_KEY: asdfasdfasdfasdfasdf + healthcheck: + test: bash -c 'curl -s "http://127.0.0.1:3567/hello" | grep "Hello"' + interval: 10s + timeout: 5s + retries: 5 + + oauth: + image: supertokens/oauth2-test:latest + platform: linux/amd64 diff --git a/examples/with-account-linking/frontend/src/LinkingPage/index.tsx b/examples/with-account-linking/frontend/src/LinkingPage/index.tsx index 1e116dd3f..8fd9051ec 100644 --- a/examples/with-account-linking/frontend/src/LinkingPage/index.tsx +++ b/examples/with-account-linking/frontend/src/LinkingPage/index.tsx @@ -54,6 +54,7 @@ export const LinkingPage: React.FC = () => { try { let response = await Passwordless.createCode({ phoneNumber, + shouldTryLinkingWithSessionUser: true, }); if (cancel) { diff --git a/examples/with-multifactorauth-phone-chooser/frontend/package.json b/examples/with-multifactorauth-phone-chooser/frontend/package.json index 9bd476ef3..8d12ddcb7 100644 --- a/examples/with-multifactorauth-phone-chooser/frontend/package.json +++ b/examples/with-multifactorauth-phone-chooser/frontend/package.json @@ -15,7 +15,7 @@ "react-dom": "^18.2.0", "react-router-dom": "^6.2.1", "react-scripts": "5.0.1", - "supertokens-auth-react": "github:supertokens/supertokens-auth-react#feat/mfa_redirect", + "supertokens-auth-react": "github:supertokens/supertokens-auth-react", "supertokens-web-js": "latest", "typescript": "^4.8.2", "web-vitals": "^2.1.4" diff --git a/examples/with-multifactorauth-phone-chooser/frontend/src/SelectPhone/index.tsx b/examples/with-multifactorauth-phone-chooser/frontend/src/SelectPhone/index.tsx index 7feaec211..34181bbbf 100644 --- a/examples/with-multifactorauth-phone-chooser/frontend/src/SelectPhone/index.tsx +++ b/examples/with-multifactorauth-phone-chooser/frontend/src/SelectPhone/index.tsx @@ -23,7 +23,10 @@ export default function SelectPhone() { navigate: nav, }); } else if (loadedInfo.user.phoneNumbers.length === 1) { - await Passwordless.createCode({ phoneNumber: loadedInfo.user.phoneNumbers[0] }); + await Passwordless.createCode({ + phoneNumber: loadedInfo.user.phoneNumbers[0], + shouldTryLinkingWithSessionUser: true, + }); await MultiFactorAuth.redirectToFactor({ factorId: MultiFactorAuth.FactorIds.OTP_PHONE, redirectBack: false, @@ -60,7 +63,7 @@ export default function SelectPhone() {
  • { - Passwordless.createCode({ phoneNumber: number }) + Passwordless.createCode({ phoneNumber: number, shouldTryLinkingWithSessionUser: true }) .then(async (info) => { if (info.status !== "OK") { setError(info.reason); @@ -72,6 +75,7 @@ export default function SelectPhone() { contactMethod: "PHONE", contactInfo: number, hasOtherPhoneNumbers: true, + shouldTryLinkingWithSessionUser: true, }, }); return MultiFactorAuth.redirectToFactor({ diff --git a/examples/with-multiple-email-sign-in/api-server/epOverride.ts b/examples/with-multiple-email-sign-in/api-server/epOverride.ts index b37bd0db3..13c151fa1 100644 --- a/examples/with-multiple-email-sign-in/api-server/epOverride.ts +++ b/examples/with-multiple-email-sign-in/api-server/epOverride.ts @@ -7,7 +7,7 @@ export function epOverride(oI: APIInterface): APIInterface { signInPOST: async function (input) { const emailField = input.formFields.find((f) => f.id === "email")!; - let primaryEmail = getPrimaryEmailFromInputEmail(emailField.value); + let primaryEmail = getPrimaryEmailFromInputEmail(emailField.value as string); if (primaryEmail !== undefined) { emailField.value = primaryEmail; } @@ -16,7 +16,7 @@ export function epOverride(oI: APIInterface): APIInterface { signUpPOST: async function (input) { const emailField = input.formFields.find((f) => f.id === "email")!; - let primaryEmail = getPrimaryEmailFromInputEmail(emailField.value); + let primaryEmail = getPrimaryEmailFromInputEmail(emailField.value as string); if (primaryEmail !== undefined) { emailField.value = primaryEmail; } diff --git a/examples/with-phone-password-mfa/api-server/index.ts b/examples/with-phone-password-mfa/api-server/index.ts index 213df7df6..61d3aa417 100644 --- a/examples/with-phone-password-mfa/api-server/index.ts +++ b/examples/with-phone-password-mfa/api-server/index.ts @@ -77,7 +77,7 @@ supertokens.init({ // We format the phone number here to get it to a standard format const emailField = input.formFields.find((field) => field.id === "email"); if (emailField) { - const phoneNumber = parsePhoneNumber(emailField.value); + const phoneNumber = parsePhoneNumber(emailField.value as string); if (phoneNumber !== undefined && phoneNumber.isValid()) { emailField.value = phoneNumber.number; } @@ -93,7 +93,7 @@ supertokens.init({ // We format the phone number here to get it to a standard format const emailField = input.formFields.find((field) => field.id === "email"); if (emailField) { - const phoneNumber = parsePhoneNumber(emailField.value); + const phoneNumber = parsePhoneNumber(emailField.value as string); if (phoneNumber !== undefined && phoneNumber.isValid()) { emailField.value = phoneNumber.number; } diff --git a/examples/with-phone-password/api-server/index.ts b/examples/with-phone-password/api-server/index.ts index 6a697598a..e7b6c7e0b 100644 --- a/examples/with-phone-password/api-server/index.ts +++ b/examples/with-phone-password/api-server/index.ts @@ -75,7 +75,7 @@ supertokens.init({ // We format the phone number here to get it to a standard format const emailField = input.formFields.find((field) => field.id === "email"); if (emailField) { - const phoneNumber = parsePhoneNumber(emailField.value); + const phoneNumber = parsePhoneNumber(emailField.value as string); if (phoneNumber !== undefined && phoneNumber.isValid()) { emailField.value = phoneNumber.number; } @@ -91,7 +91,7 @@ supertokens.init({ // We format the phone number here to get it to a standard format const emailField = input.formFields.find((field) => field.id === "email"); if (emailField) { - const phoneNumber = parsePhoneNumber(emailField.value); + const phoneNumber = parsePhoneNumber(emailField.value as string); if (phoneNumber !== undefined && phoneNumber.isValid()) { emailField.value = phoneNumber.number; } diff --git a/examples/with-svelte-react-thirdpartyemailpassword/package.json b/examples/with-svelte-react-thirdpartyemailpassword/package.json index a744965ad..c89e4172a 100644 --- a/examples/with-svelte-react-thirdpartyemailpassword/package.json +++ b/examples/with-svelte-react-thirdpartyemailpassword/package.json @@ -43,6 +43,6 @@ "sirv-cli": "^2.0.0", "supertokens-auth-react": "latest", "supertokens-node": "latest", - "svelte-navigator": "^3.1.5" + "svelte-navigator": "latest" } } diff --git a/hooks/populate-hook-constants.sh b/hooks/populate-hook-constants.sh new file mode 100755 index 000000000..c5a56d066 --- /dev/null +++ b/hooks/populate-hook-constants.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Look for the version string with additional handling for: +# - Abitrary Spaces: ` *` +# - Extracting the version into a match group: `(...)` +# - Substituting the matched string with the match group: `/\1/` +export packageVersion=$( echo $(cat package.json | jq .version) | sed -n 's/^["]\([0-9\.]*\).*/\1/p' ) +export packageVersionXy=$( echo $(cat package.json | jq .version) | sed -n 's/^["]\([0-9]*\.[0-9]*\).*/\1/p' ) +export packageLockVersion=$( echo $(cat package-lock.json | jq .version) | sed -n 's/["]\([0-9\.]*\).*/\1/p' ) +export packageLockVersionXy=$( echo $(cat package-lock.json | jq .version) | sed -n 's/["]\([0-9]*\.[0-9]*\).*/\1/p' ) + +export newestVersion=$( if [[ "$packageVersion" > "$packageLockVersion" ]]; then echo "$packageVersion"; else echo "$packageLockVersion"; fi ) + +# Target branch of the PR. +# Ideally, this is all we want to check. +if [[ "$GITHUB_BASE_REF" != "" ]] +then + export targetBranch="$GITHUB_BASE_REF" +else # Fallback to current branch if not in a PR + export targetBranch=$(git branch --show-current 2> /dev/null) || export targetBranch="(unnamed branch)" # Get current branch +fi +export targetBranch=${targetBranch##refs/heads/} # Remove refs/heads/ if present diff --git a/mocha-multi-reporters.json b/mocha-multi-reporters.json new file mode 100644 index 000000000..bf76ca301 --- /dev/null +++ b/mocha-multi-reporters.json @@ -0,0 +1,9 @@ +{ + "reporterEnabled": "mochawesome, mocha-junit-reporter", + "mochaJunitReporterReporterOptions": { + "mochaFile": "test_report/test-results.xml" + }, + "mochawesomeReporterOptions": { + "reportDir": "test_report/mochawesome" + } +} diff --git a/package-lock.json b/package-lock.json index 1b47f9694..ccdb3f4e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,9 @@ "mocha": "^10.2.0", "mocha-junit-reporter": "^2.2.1", "mocha-multi": "^1.1.7", + "mocha-multi-reporters": "^1.5.1", "mocha-split-tests": "github:rishabhpoddar/mocha-split-tests", + "mochawesome": "^7.1.3", "npm-run-all": "^4.1.5", "postcss": "^8.4.19", "postcss-import": "^15.0.0", @@ -2459,57 +2461,6 @@ "react": ">=16.8.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", @@ -2527,312 +2478,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -7514,185 +7159,32 @@ "@swc/core-linux-arm64-gnu": "1.10.1", "@swc/core-linux-arm64-musl": "1.10.1", "@swc/core-linux-x64-gnu": "1.10.1", - "@swc/core-linux-x64-musl": "1.10.1", - "@swc/core-win32-arm64-msvc": "1.10.1", - "@swc/core-win32-ia32-msvc": "1.10.1", - "@swc/core-win32-x64-msvc": "1.10.1" - }, - "peerDependencies": { - "@swc/helpers": "*" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.1.tgz", - "integrity": "sha512-NyELPp8EsVZtxH/mEqvzSyWpfPJ1lugpTQcSlMduZLj1EASLO4sC8wt8hmL1aizRlsbjCX+r0PyL+l0xQ64/6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.1.tgz", - "integrity": "sha512-L4BNt1fdQ5ZZhAk5qoDfUnXRabDOXKnXBxMDJ+PWLSxOGBbWE6aJTnu4zbGjJvtot0KM46m2LPAPY8ttknqaZA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.1.tgz", - "integrity": "sha512-Y1u9OqCHgvVp2tYQAJ7hcU9qO5brDMIrA5R31rwWQIAKDkJKtv3IlTHF0hrbWk1wPR0ZdngkQSJZple7G+Grvw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.1.tgz", - "integrity": "sha512-tNQHO/UKdtnqjc7o04iRXng1wTUXPgVd8Y6LI4qIbHVoVPwksZydISjMcilKNLKIwOoUQAkxyJ16SlOAeADzhQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.1.tgz", - "integrity": "sha512-x0L2Pd9weQ6n8dI1z1Isq00VHFvpBClwQJvrt3NHzmR+1wCT/gcYl1tp9P5xHh3ldM8Cn4UjWCw+7PaUgg8FcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.1.tgz", - "integrity": "sha512-yyYEwQcObV3AUsC79rSzN9z6kiWxKAVJ6Ntwq2N9YoZqSPYph+4/Am5fM1xEQYf/kb99csj0FgOelomJSobxQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.1.tgz", - "integrity": "sha512-tcaS43Ydd7Fk7sW5ROpaf2Kq1zR+sI5K0RM+0qYLYYurvsJruj3GhBCaiN3gkzd8m/8wkqNqtVklWaQYSDsyqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.1.tgz", - "integrity": "sha512-D3Qo1voA7AkbOzQ2UGuKNHfYGKL6eejN8VWOoQYtGHHQi1p5KK/Q7V1ku55oxXBsj79Ny5FRMqiRJpVGad7bjQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.1.tgz", - "integrity": "sha512-WalYdFoU3454Og+sDKHM1MrjvxUGwA2oralknXkXL8S0I/8RkWZOB++p3pLaGbTvOO++T+6znFbQdR8KRaa7DA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" + "@swc/core-linux-x64-musl": "1.10.1", + "@swc/core-win32-arm64-msvc": "1.10.1", + "@swc/core-win32-ia32-msvc": "1.10.1", + "@swc/core-win32-x64-msvc": "1.10.1" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } } }, - "node_modules/@swc/core-win32-x64-msvc": { + "node_modules/@swc/core-darwin-arm64": { "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.1.tgz", - "integrity": "sha512-JWobfQDbTnoqaIwPKQ3DVSywihVXlQMbDuwik/dDWlj33A8oEHcjPOGs4OqcA3RHv24i+lfCQpM3Mn4FAMfacA==", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.1.tgz", + "integrity": "sha512-NyELPp8EsVZtxH/mEqvzSyWpfPJ1lugpTQcSlMduZLj1EASLO4sC8wt8hmL1aizRlsbjCX+r0PyL+l0xQ64/6Q==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ - "win32" + "darwin" ], "engines": { "node": ">=10" @@ -11420,6 +10912,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -14474,6 +13976,13 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fsu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz", + "integrity": "sha512-xQVsnjJ/5pQtcKh+KjUoZGzVWn4uNkchxTF6Lwjr4Gf7nQr8fmUfhKJ62zE77+xQg9xnxi5KUps7XGs+VC986A==", + "dev": true, + "license": "MIT" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -17840,6 +17349,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -18113,6 +17629,34 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -18851,6 +18395,23 @@ "mocha": ">=2.2.0 <7 || >=9" } }, + "node_modules/mocha-multi-reporters": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz", + "integrity": "sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "mocha": ">=3.1.2" + } + }, "node_modules/mocha-split-tests": { "version": "1.4.0", "resolved": "git+ssh://git@github.com/rishabhpoddar/mocha-split-tests.git#b0bd99a7d5870493dbe921dbdd5195b47e555035", @@ -18864,121 +18425,316 @@ "mocha-split-tests": "bin/mocha-split-tests" }, "peerDependencies": { - "@wdio/reporter": ">=5", - "mocha": ">=6" + "@wdio/reporter": ">=5", + "mocha": ">=6" + } + }, + "node_modules/mocha-split-tests/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/mocha-split-tests/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/mocha/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mochawesome": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/mochawesome/-/mochawesome-7.1.3.tgz", + "integrity": "sha512-Vkb3jR5GZ1cXohMQQ73H3cZz7RoxGjjUo0G5hu0jLaW+0FdUxUwg3Cj29bqQdh0rFcnyV06pWmqmi5eBPnEuNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "diff": "^5.0.0", + "json-stringify-safe": "^5.0.1", + "lodash.isempty": "^4.4.0", + "lodash.isfunction": "^3.0.9", + "lodash.isobject": "^3.0.2", + "lodash.isstring": "^4.0.1", + "mochawesome-report-generator": "^6.2.0", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "mocha": ">=7" } }, - "node_modules/mocha-split-tests/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "node_modules/mochawesome-report-generator": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mochawesome-report-generator/-/mochawesome-report-generator-6.2.0.tgz", + "integrity": "sha512-Ghw8JhQFizF0Vjbtp9B0i//+BOkV5OWcQCPpbO0NGOoxV33o+gKDYU0Pr2pGxkIHnqZ+g5mYiXF7GMNgAcDpSg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 10" + "dependencies": { + "chalk": "^4.1.2", + "dateformat": "^4.5.1", + "escape-html": "^1.0.3", + "fs-extra": "^10.0.0", + "fsu": "^1.1.1", + "lodash.isfunction": "^3.0.9", + "opener": "^1.5.2", + "prop-types": "^15.7.2", + "tcomb": "^3.2.17", + "tcomb-validation": "^3.3.0", + "validator": "^13.6.0", + "yargs": "^17.2.1" + }, + "bin": { + "marge": "bin/cli.js" } }, - "node_modules/mocha-split-tests/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "node_modules/mochawesome-report-generator/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/mocha/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/mochawesome-report-generator/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/mochawesome-report-generator/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", + "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=12" } }, - "node_modules/mocha/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/mochawesome-report-generator/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=12" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/mochawesome-report-generator/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/mocha/node_modules/strip-ansi": { + "node_modules/mochawesome-report-generator/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -18991,23 +18747,20 @@ "node": ">=8" } }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/mochawesome-report-generator/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">=8" } }, - "node_modules/mocha/node_modules/wrap-ansi": { + "node_modules/mochawesome-report-generator/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -19025,7 +18778,7 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/mocha/node_modules/y18n": { + "node_modules/mochawesome-report-generator/node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", @@ -19035,23 +18788,122 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/mochawesome-report-generator/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "string-width": "^4.2.0", + "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mochawesome-report-generator/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/mochawesome/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/mochawesome/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/mochawesome/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mochawesome/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mochawesome/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mochawesome/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mochawesome/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" } }, "node_modules/module-definition": { @@ -19942,6 +19794,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -24832,6 +24694,23 @@ "dev": true, "license": "ISC" }, + "node_modules/tcomb": { + "version": "3.2.29", + "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", + "integrity": "sha512-di2Hd1DB2Zfw6StGv861JoAF5h/uQVu/QJp2g8KVbtfKnoHdBQl5M32YWq6mnSYBQ1vFFrns5B1haWJL7rKaOQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tcomb-validation": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", + "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tcomb": "^3.0.0" + } + }, "node_modules/telejson": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/telejson/-/telejson-7.2.0.tgz", @@ -26274,6 +26153,16 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 54f0ff136..5f85f4aa2 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,9 @@ "mocha": "^10.2.0", "mocha-junit-reporter": "^2.2.1", "mocha-multi": "^1.1.7", + "mocha-multi-reporters": "^1.5.1", "mocha-split-tests": "github:rishabhpoddar/mocha-split-tests", + "mochawesome": "^7.1.3", "npm-run-all": "^4.1.5", "postcss": "^8.4.19", "postcss-import": "^15.0.0", @@ -114,7 +116,6 @@ "test-e2e-react16": "RUN_REACT_16_TESTS=true ./test/startTestApp.sh 8082", "test-e2e-with-non-node": "./test/startTestApp.sh 8083", "test-unit": "TEST_MODE=testing jest --silent -c jest.config.js --runInBand", - "test-visual": "SPEC_FILES=test/visual/**/*.test.js npx percy exec -- npm run test-e2e", "prep-test-app": "./test/prepTestApp.sh $SRC", "build-check": "cd lib && npx tsc -p tsconfig.json --noEmit && cd ../test/with-typescript && npx tsc -p tsconfig.json", "tsc": "cd lib && npx tsc -p tsconfig.json", diff --git a/test/end-to-end/accountlinking.test.js b/test/end-to-end/accountlinking.test.js index 92bedc11e..3098cf993 100644 --- a/test/end-to-end/accountlinking.test.js +++ b/test/end-to-end/accountlinking.test.js @@ -18,7 +18,6 @@ co * */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, clickOnProviderButton, @@ -28,11 +27,7 @@ import { submitForm, waitForSTElement, getPasswordlessDevice, - setPasswordlessFlowType, getFeatureFlags, - isReact16, - setAccountLinkingConfig, - signUp, toggleSignInSignUp, getInputAdornmentsSuccess, getInputAdornmentsError, @@ -43,11 +38,14 @@ import { submitFormReturnRequestAndResponse, getTextByDataSupertokens, sendEmailResetPasswordSuccessMessage, - changeEmail, - backendBeforeEach, setupBrowser, + setupCoreApp, + setupST, + backendHook, + screenshotOnFailure, + tryPasswordlessSignInUp, } from "../helpers"; -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_IN_UP_API, RESET_PASSWORD_API } from "../constants"; +import { TEST_CLIENT_BASE_URL, RESET_PASSWORD_API } from "../constants"; /* * Tests. @@ -56,47 +54,40 @@ describe("SuperTokens Account linking", function () { let browser; let page; let consoleLogs; + let coreUrl; + + const passwordlessFlowType = "USER_INPUT_CODE_AND_MAGIC_LINK"; + const passwordlessContactMethod = "EMAIL_OR_PHONE"; before(async function () { const features = await getFeatureFlags(); if (!features.includes("accountlinking")) { this.skip(); } + + coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); }); describe("Recipe combination tests", () => { before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - + await backendHook("before"); browser = await setupBrowser(); + }); + + beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); + + consoleLogs = []; page.on("console", (consoleObj) => { const log = consoleObj.text(); if (log.startsWith("ST_LOGS")) { consoleLogs.push(log); } }); - }); - - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - beforeEach(async function () { - consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - await setPasswordlessFlowType("EMAIL_OR_PHONE", "USER_INPUT_CODE_AND_MAGIC_LINK"); + await Promise.all([ page.goto( `${TEST_CLIENT_BASE_URL}/auth/?authRecipe=thirdpartypasswordless&passwordlessContactMethodType=EMAIL_OR_PHONE` @@ -107,7 +98,15 @@ describe("SuperTokens Account linking", function () { }); afterEach(async function () { + await screenshotOnFailure(this, browser); await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser.close(); + await backendHook("after"); }); describe("account consolidation", () => { @@ -124,8 +123,20 @@ describe("SuperTokens Account linking", function () { const doLogin2 = login2[1]; it(`should work for ${login1[0]} - ${login2[0]} w/ email verification not required`, async () => { + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }); + const email = `test-user+${Date.now()}@supertokens.com`; - await setAccountLinkingConfig(true, true, false); // 1. Sign up with login method 1 await doLogin1(page, email); @@ -153,10 +164,22 @@ describe("SuperTokens Account linking", function () { if (login2[0] !== "emailpassword") { it(`should work for ${login1[0]} - ${login2[0]} w/ email verification required`, async () => { + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); + await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); const email = `test-user+${Date.now()}@supertokens.com`; - await setAccountLinkingConfig(true, true, true); // 1. Sign up with login method 1 await doLogin1(page, email); if (login1[0] === "emailpassword") { @@ -196,10 +219,22 @@ describe("SuperTokens Account linking", function () { }); } else { it(`should work for ${login1[0]} - password reset (invite link flow) w/ email verification required`, async () => { + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); + await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); const email = `test-user+${Date.now()}@supertokens.com`; - await setAccountLinkingConfig(true, true, true); // 1. Sign up with login method 1 await doLogin1(page, email); if (login1[0] === "emailpassword") { @@ -245,9 +280,20 @@ describe("SuperTokens Account linking", function () { describe("conflicting accounts", () => { it("should not allow sign up w/ emailpassword in case of conflict", async function () { - const email = `test-user+${Date.now()}@supertokens.com`; + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); - await setAccountLinkingConfig(true, true, true); + const email = `test-user+${Date.now()}@supertokens.com`; // 1. Sign up with credentials await tryPasswordlessSignInUp(page, email); @@ -275,9 +321,22 @@ describe("SuperTokens Account linking", function () { }); it("should not allow sign in w/ an unverified emailpassword user in case of conflict", async function () { - const email = `test-user+${Date.now()}@supertokens.com`; + // Use a common appId over the test to allow re-inits + const coreUrl = await setupCoreApp(); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: false, + shouldRequireVerification: true, + }, + }, + }); - await setAccountLinkingConfig(true, false); + const email = `test-user+${Date.now()}@supertokens.com`; // 1. Sign up without account linking with an unverified ep and tp users & log out // We need both, because when setting up the pwless user we'll link to the older one await tryThirdPartySignInUp(page, email, false); @@ -288,7 +347,18 @@ describe("SuperTokens Account linking", function () { await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); await logOut(page); - await setAccountLinkingConfig(true, true, false); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }); // 2. Sign up with passwordless to create a primary user await tryPasswordlessSignInUp(page, email); @@ -299,7 +369,18 @@ describe("SuperTokens Account linking", function () { await waitForSTElement(page, `input[name=email]`); - await setAccountLinkingConfig(true, true, true); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); // 4. Try sign in with emailpassword await Promise.all([ @@ -320,9 +401,20 @@ describe("SuperTokens Account linking", function () { }); it("should not allow sign up w/ an unverified thirdparty user in case of conflict", async function () { - const email = `test-user+${Date.now()}@supertokens.com`; + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); - await setAccountLinkingConfig(true, true, true); + const email = `test-user+${Date.now()}@supertokens.com`; // 1. Sign up with credentials await tryPasswordlessSignInUp(page, email); @@ -344,10 +436,21 @@ describe("SuperTokens Account linking", function () { }); it("should not allow using thirdparty sign in with changed email in case of conflict", async function () { + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); + const email = `test-user+${Date.now()}@supertokens.com`; const email2 = `test-user-2+${Date.now()}@supertokens.com`; - - await setAccountLinkingConfig(true, true, true); // 1. Sign up with credentials await tryPasswordlessSignInUp(page, email); @@ -386,16 +489,40 @@ describe("SuperTokens Account linking", function () { }); it("should not allow sign in w/ an unverified thirdparty user in case of conflict", async function () { - const email = `test-user+${Date.now()}@supertokens.com`; + // Use a common appId over the test to allow re-inits + const coreUrl = await setupCoreApp(); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: false, + shouldRequireVerification: true, + }, + }, + }); - await setAccountLinkingConfig(true, false); + const email = `test-user+${Date.now()}@supertokens.com`; await tryEmailPasswordSignUp(page, email); await logOut(page); // 1. Sign up without account linking with an unverified tp user & log out await tryThirdPartySignInUp(page, email, false); await logOut(page); - await setAccountLinkingConfig(true, true, false); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }); // 2. Sign up with passwordless to create a primary user await tryPasswordlessSignInUp(page, email); @@ -406,7 +533,18 @@ describe("SuperTokens Account linking", function () { await waitForSTElement(page, `input[name=email]`); - await setAccountLinkingConfig(true, true, true); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); // 4. Try sign in with third party await tryThirdPartySignInUp(page, email, false); @@ -420,12 +558,36 @@ describe("SuperTokens Account linking", function () { it("should not allow sign up w/ passwordless if it conflicts with an unverified user", async function () { const email = `test-user+${Date.now()}@supertokens.com`; - await setAccountLinkingConfig(true, false); + // Use a common appId over the test to allow re-inits + const coreUrl = await setupCoreApp(); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: false, + shouldRequireVerification: true, + }, + }, + }); // 1. Sign up without account linking with an unverified tp user & log out await tryEmailPasswordSignUp(page, email); await logOut(page); - await setAccountLinkingConfig(true, true, true); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); // 2. Sign up with passwordless await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); await Promise.all([ @@ -445,13 +607,36 @@ describe("SuperTokens Account linking", function () { it("should not allow sign up w/ passwordless if it conflicts with an unverified user", async function () { const email = `test-user+${Date.now()}@supertokens.com`; - - await setAccountLinkingConfig(true, false); + // Use a common appId over the test to allow re-inits + const coreUrl = await setupCoreApp(); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: false, + shouldRequireVerification: true, + }, + }, + }); // 1. Sign up without account linking with an unverified tp user & log out await tryEmailPasswordSignUp(page, email); await logOut(page); - await setAccountLinkingConfig(true, true, true); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); // 2. Sign in with passwordless await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); await Promise.all([ @@ -473,7 +658,20 @@ describe("SuperTokens Account linking", function () { const email = `test-user+${Date.now()}@supertokens.com`; const email2 = `test-user2+${Date.now()}@supertokens.com`; - await setAccountLinkingConfig(true, true, false); + // Use a common appId over the test to allow re-inits + const coreUrl = await setupCoreApp(); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }); // 1. Sign up without account linking with an unverified tp user & log out await tryThirdPartySignInUp(page, email2, false); await logOut(page); @@ -486,12 +684,34 @@ describe("SuperTokens Account linking", function () { await tryThirdPartySignInUp(page, email, false, email2); await logOut(page); - await setAccountLinkingConfig(true, false, false); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: false, + shouldRequireVerification: false, + }, + }, + }); // 4. Add a recipe level user await tryEmailPasswordSignUp(page, email); await logOut(page); - await setAccountLinkingConfig(true, true, true); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); // 5. Try resetting the password of the recipe leve user await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth/reset-password?authRecipe=emailpassword`), @@ -513,7 +733,20 @@ describe("SuperTokens Account linking", function () { const email = `test-user+${Date.now()}@supertokens.com`; await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); - await setAccountLinkingConfig(true, false); + // Use a common appId over the test to allow re-inits + const coreUrl = await setupCoreApp(); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: false, + shouldRequireVerification: true, + }, + }, + }); // 1. Sign up without account linking with an unverified tp user & log out await tryEmailPasswordSignUp(page, email); await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); @@ -528,7 +761,18 @@ describe("SuperTokens Account linking", function () { await logOut(page); - await setAccountLinkingConfig(true, true, false); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }); // 2. Sign up with passwordless to create a primary user await tryPasswordlessSignInUp(page, email); @@ -539,7 +783,18 @@ describe("SuperTokens Account linking", function () { await waitForSTElement(page, `input[name=email]`); - await setAccountLinkingConfig(true, true, true); + await setupST({ + coreUrl, + passwordlessFlowType, + passwordlessContactMethod, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); // 4. Try sign in with emailpassword await Promise.all([ @@ -577,26 +832,6 @@ async function tryEmailPasswordSignUp(page, email) { await new Promise((res) => setTimeout(res, 250)); } -async function tryPasswordlessSignInUp(page, email) { - await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=passwordless`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - await setInputValues(page, [{ name: "email", value: email }]); - await submitForm(page); - - await waitForSTElement(page, "[data-supertokens~=input][name=userInputCode]"); - - const loginAttemptInfo = JSON.parse( - await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo")) - ); - const device = await getPasswordlessDevice(loginAttemptInfo); - await setInputValues(page, [{ name: "userInputCode", value: device.codes[0].userInputCode }]); - await submitForm(page); -} - async function tryThirdPartySignInUp(page, email, isVerified = true, userId = email) { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=thirdparty`), diff --git a/test/end-to-end/emailverification.test.js b/test/end-to-end/emailverification.test.js index 4c80e01dc..586d1cd8a 100644 --- a/test/end-to-end/emailverification.test.js +++ b/test/end-to-end/emailverification.test.js @@ -18,7 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import fetch from "isomorphic-fetch"; import { SEND_VERIFY_EMAIL_API, @@ -50,955 +49,825 @@ import { waitForSTElement, isGeneralErrorSupported, setGeneralErrorToLocalStorage, - isAccountLinkingSupported, - backendBeforeEach, getDefaultSignUpFieldValues, getTestEmail, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + getLogoutButton, + setupST, } from "../helpers"; -describe("SuperTokens Email Verification", function () { +describe("Email Verification", () => { let browser; let page; let consoleLogs; - let accountLinkingSupported; before(async function () { - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + await backendHook("before"); + browser = await setupBrowser(); - accountLinkingSupported = await isAccountLinkingSupported(); + }); + + beforeEach(async function () { + await backendHook("beforeEach"); + + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); page = await browser.newPage(); + + consoleLogs = []; + page.on("console", (consoleObj) => { + const log = consoleObj.text(); + if (log.startsWith("ST_LOGS")) { + consoleLogs.push(log); + } + }); + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); + await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - afterEach(async function () { await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } + await page?.close(); + await backendHook("afterEach"); }); - describe("Email verification screen", function () { - beforeEach(async function () { - page = await browser.newPage(); - consoleLogs = []; - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - // console.log(log); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); - } - }); - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - }); - - it("Should redirect to auth from email verification protected page if the user is deleted", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); + after(async function () { + await browser?.close(); + await backendHook("after"); + }); - const email = await getTestEmail(); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email }); - await signUp(page, fieldValues, postValues, "emailpassword"); + describe("Core Functionality", function () { + describe("Email verification screen", function () { + it("Should redirect to auth from email verification protected page if the user is deleted", async function () { + await toggleSignInSignUp(page); - await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + const email = await getTestEmail(); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email }); + await signUp(page, fieldValues, postValues, "emailpassword"); - await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/deleteUser`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - email, - rid: "emailpassword", - }), - }); - await new Promise((r) => setTimeout(r, 11000)); - consoleLogs = []; - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/dashboard`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - // In strict mode useEffects may be called twice in development mode, - // but sometimes the second call is aborted by the navigation in the first - if ( - consoleLogs[consoleLogs.length - 1] === "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH" && - consoleLogs[consoleLogs.length - 2] === "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH" - ) { - consoleLogs.pop(); - } + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); - await waitForUrl(page, "/auth/"); - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - "ST_LOGS SESSION ON_HANDLE_EVENT UNAUTHORISED", - "ST_LOGS SESSION ON_HANDLE_EVENT UNAUTHORISED", - "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", - ]); - }); - - it("Should redirect to auth if the user has been deleted", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); - const email = await getTestEmail(); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email }); - await signUp(page, fieldValues, postValues, "emailpassword"); - - await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); - await waitForUrl(page, "/auth/verify-email"); + await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/deleteUser`, { + method: "POST", + headers: [["content-type", "application/json"]], + body: JSON.stringify({ + email, + rid: "emailpassword", + }), + }); + await new Promise((r) => setTimeout(r, 11000)); + consoleLogs = []; + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/dashboard`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + // In strict mode useEffects may be called twice in development mode, + // but sometimes the second call is aborted by the navigation in the first + if ( + consoleLogs[consoleLogs.length - 1] === "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH" && + consoleLogs[consoleLogs.length - 2] === "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH" + ) { + consoleLogs.pop(); + } - await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/deleteUser`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - email, - rid: "emailpassword", - }), + await waitForUrl(page, "/auth/"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", + "ST_LOGS SESSION ON_HANDLE_EVENT UNAUTHORISED", + "ST_LOGS SESSION ON_HANDLE_EVENT UNAUTHORISED", + "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", + ]); }); - await new Promise((r) => setTimeout(r, 11000)); - - consoleLogs = []; - await page.reload({ waitUntil: ["networkidle0"] }); - // Click on Logout should remove session and redirect to login page - await waitForUrl(page, "/auth/"); - - // In strict mode useEffects may be called twice in development mode, - // but sometimes the second call is aborted by the navigation in the first - if ( - consoleLogs[consoleLogs.length - 1] === "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH" && - consoleLogs[consoleLogs.length - 2] === "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH" - ) { - consoleLogs.pop(); - } - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - "ST_LOGS SESSION ON_HANDLE_EVENT UNAUTHORISED", - "ST_LOGS SESSION ON_HANDLE_EVENT UNAUTHORISED", - "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", - ]); - }); + it("Should redirect to auth if the user has been deleted", async function () { + await toggleSignInSignUp(page); + const email = await getTestEmail(); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email }); + await signUp(page, fieldValues, postValues, "emailpassword"); - it("Should redirect to login page when email verification screen is accessed without a valid session", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await waitForUrl(page, "/auth/"); - }); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + await waitForUrl(page, "/auth/verify-email"); - it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); - await defaultSignUp(page); - await waitForUrl(page, "/auth/verify-email"); - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS EMAIL_PASSWORD OVERRIDE DOES_EMAIL_EXIST", - "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_EXISTS", - "ST_LOGS EMAIL_PASSWORD OVERRIDE SIGN_UP", - "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_PASSWORD_SIGN_UP", - "ST_LOGS SESSION ON_HANDLE_EVENT SESSION_CREATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT SUCCESS", - "ST_LOGS EMAIL_VERIFICATION GET_REDIRECTION_URL VERIFY_EMAIL", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", - ]); - }); + await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/deleteUser`, { + method: "POST", + headers: [["content-type", "application/json"]], + body: JSON.stringify({ + email, + rid: "emailpassword", + }), + }); - it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (w/ leading slash) and newUser", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe2@supertokens.io" }); - await signUp(page, fieldValues, postValues, "emailpassword"); + consoleLogs = []; + await page.reload({ waitUntil: ["networkidle0"] }); + // Click on Logout should remove session and redirect to login page + await waitForUrl(page, "/auth/"); + + // In strict mode useEffects may be called twice in development mode, + // but sometimes the second call is aborted by the navigation in the first + if ( + consoleLogs[consoleLogs.length - 1] === "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH" && + consoleLogs[consoleLogs.length - 2] === "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH" + ) { + consoleLogs.pop(); + } - await waitForUrl(page, "/auth/verify-email"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", + "ST_LOGS SESSION ON_HANDLE_EVENT UNAUTHORISED", + "ST_LOGS SESSION ON_HANDLE_EVENT UNAUTHORISED", + "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", + ]); + }); - // we wait for email to be created - await new Promise((r) => setTimeout(r, 1000)); + it("Should redirect to login page when email verification screen is accessed without a valid session", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await waitForUrl(page, "/auth/"); + }); - // we fetch the email verification link and go to that - const latestURLWithToken = await getLatestURLWithToken(); - await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); + it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified", async function () { + await toggleSignInSignUp(page); + await defaultSignUp(page); + await waitForUrl(page, "/auth/verify-email"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS EMAIL_PASSWORD OVERRIDE DOES_EMAIL_EXIST", + "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_EXISTS", + "ST_LOGS EMAIL_PASSWORD OVERRIDE SIGN_UP", + "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_PASSWORD_SIGN_UP", + "ST_LOGS SESSION ON_HANDLE_EVENT SESSION_CREATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT SUCCESS", + "ST_LOGS EMAIL_VERIFICATION GET_REDIRECTION_URL VERIFY_EMAIL", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", + "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", + ]); + }); - // click on the continue button - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (w/ leading slash) and newUser", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%2Fredirect-here%3Ffoo%3Dbar`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues(); + await signUp(page, fieldValues, postValues, "emailpassword"); - // check that we are in /redirect-here?foo=bar - await waitForUrl(page, "/redirect-here?foo=bar", false); - }); + await waitForUrl(page, "/auth/verify-email"); - it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (w/o leading slash) and newUser", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: await getTestEmail() }); - await signUp(page, fieldValues, postValues, "emailpassword"); + // we wait for email to be created + await new Promise((r) => setTimeout(r, 1000)); - await waitForUrl(page, "/auth/verify-email"); + // we fetch the email verification link and go to that + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); - // we wait for email to be created - await new Promise((r) => setTimeout(r, 1000)); + // click on the continue button + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - // we fetch the email verification link and go to that - const latestURLWithToken = await getLatestURLWithToken(); - await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); + // check that we are in /redirect-here?foo=bar + await waitForUrl(page, "/redirect-here?foo=bar", false); + }); - // click on the continue button - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (w/o leading slash) and newUser", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=%3Ffoo%3Dbar`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues(); + await signUp(page, fieldValues, postValues, "emailpassword"); - // check that we are in /?foo=bar - await waitForUrl(page, "/?foo=bar", false); - }); + await waitForUrl(page, "/auth/verify-email"); - it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (query params + fragment) and newUser", async function () { - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?redirectToPath=${encodeURIComponent( - "/redirect-here?foo=bar#cell=4,1-6,2" - )}` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: await getTestEmail() }); - await signUp(page, fieldValues, postValues, "emailpassword"); + // we wait for email to be created + await new Promise((r) => setTimeout(r, 1000)); - await waitForUrl(page, "/auth/verify-email"); + // we fetch the email verification link and go to that + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); - // we wait for email to be created - await new Promise((r) => setTimeout(r, 1000)); + // click on the continue button + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - // we fetch the email verification link and go to that - const latestURLWithToken = await getLatestURLWithToken(); - await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); + // check that we are in /?foo=bar + await waitForUrl(page, "/?foo=bar", false); + }); - // click on the continue button - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (query params + fragment) and newUser", async function () { + await Promise.all([ + page.goto( + `${TEST_CLIENT_BASE_URL}/auth?redirectToPath=${encodeURIComponent( + "/redirect-here?foo=bar#cell=4,1-6,2" + )}` + ), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues(); + await signUp(page, fieldValues, postValues, "emailpassword"); + + await waitForUrl(page, "/auth/verify-email"); + + // we wait for email to be created + await new Promise((r) => setTimeout(r, 1000)); + + // we fetch the email verification link and go to that + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); + + // click on the continue button + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + await waitForUrl(page, "/redirect-here?foo=bar#cell=4,1-6,2", false); + }); - await waitForUrl(page, "/redirect-here?foo=bar#cell=4,1-6,2", false); - }); + it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (only fragment) and newUser", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=${encodeURIComponent("#cell=4,1-6,2")}`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues(); + await signUp(page, fieldValues, postValues, "emailpassword"); - it("Should redirect to verify email screen on successful sign up when mode is REQUIRED and email is not verified and then post verification should redirect with original redirectPath (only fragment) and newUser", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?redirectToPath=${encodeURIComponent("#cell=4,1-6,2")}`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: await getTestEmail() }); - await signUp(page, fieldValues, postValues, "emailpassword"); + await waitForUrl(page, "/auth/verify-email"); - await waitForUrl(page, "/auth/verify-email"); + // we wait for email to be created + await new Promise((r) => setTimeout(r, 1000)); - // we wait for email to be created - await new Promise((r) => setTimeout(r, 1000)); + // we fetch the email verification link and go to that + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); - // we fetch the email verification link and go to that - const latestURLWithToken = await getLatestURLWithToken(); - await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); + // click on the continue button + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - // click on the continue button - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + // check that we are in /#cell=4,1-6,2 + await waitForUrl(page, "/#cell=4,1-6,2", false); + }); - // check that we are in /#cell=4,1-6,2 - await waitForUrl(page, "/#cell=4,1-6,2", false); - }); + it("Should redirect to verify email screen on successful sign in when mode is REQUIRED and email is not verified", async function () { + await toggleSignInSignUp(page); + await defaultSignUp(page); + await logoutFromEmailVerification(page); + await waitForUrl(page, "/auth/"); + consoleLogs = []; + + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await setInputValues(page, [ + { name: "email", value: "john.doe@supertokens.io" }, + { name: "password", value: "Str0ngP@ssw0rd" }, + ]); + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await waitForUrl(page, "/auth/verify-email"); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailResend']"); + + // Click on resend email should show "Email Resent" success message + await sendVerifyEmail(page); + await page.waitForResponse( + (response) => response.url() === SEND_VERIFY_EMAIL_API && response.status() === 200 + ); + const generalSuccess = await getGeneralSuccess(page); + assert.deepStrictEqual(generalSuccess, "Email resent"); + + // Click on Logout should remove session and redirect to login page + await Promise.all([ + logoutFromEmailVerification(page), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await waitForUrl(page, "/auth/"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS EMAIL_PASSWORD OVERRIDE SIGN_IN", + "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_PASSWORD_SIGN_IN", + "ST_LOGS SESSION ON_HANDLE_EVENT SESSION_CREATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT SUCCESS", + "ST_LOGS EMAIL_VERIFICATION GET_REDIRECTION_URL VERIFY_EMAIL", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", + "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", + "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", + "ST_LOGS SESSION OVERRIDE SIGN_OUT", + "ST_LOGS SESSION PRE_API_HOOKS SIGN_OUT", + "ST_LOGS SESSION ON_HANDLE_EVENT SIGN_OUT", + "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", + ]); + }); - it("Should redirect to verify email screen on successful sign in when mode is REQUIRED and email is not verified", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await setInputValues(page, [ - { name: "email", value: "john.doe@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - ]); - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await new Promise((r) => setTimeout(r, 2000)); - await waitForUrl(page, "/auth/verify-email"); - // Click on resend email should show "Email Resent" success message - await sendVerifyEmail(page); - await page.waitForResponse( - (response) => response.url() === SEND_VERIFY_EMAIL_API && response.status() === 200 - ); - const generalSuccess = await getGeneralSuccess(page); - assert.deepStrictEqual(generalSuccess, "Email resent"); - - // Click on Logout should remove session and redirect to login page - await Promise.all([ - logoutFromEmailVerification(page), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await waitForUrl(page, "/auth/"); - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS EMAIL_PASSWORD OVERRIDE SIGN_IN", - "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_PASSWORD_SIGN_IN", - "ST_LOGS SESSION ON_HANDLE_EVENT SESSION_CREATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT SUCCESS", - "ST_LOGS EMAIL_VERIFICATION GET_REDIRECTION_URL VERIFY_EMAIL", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", - "ST_LOGS SESSION OVERRIDE SIGN_OUT", - "ST_LOGS SESSION PRE_API_HOOKS SIGN_OUT", - "ST_LOGS SESSION ON_HANDLE_EVENT SIGN_OUT", - "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", - ]); - }); + it("Should redirect to verify email screen on successful sign in when mode is REQUIRED after unverify", async function () { + await toggleSignInSignUp(page); + await defaultSignUp(page); + await logoutFromEmailVerification(page); + await waitForUrl(page, "/auth/"); + consoleLogs = []; - it("Should redirect to verify email screen on successful sign in when mode is REQUIRED after unverify", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await setInputValues(page, [ - { name: "email", value: "john.doe@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - ]); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await setInputValues(page, [ + { name: "email", value: "john.doe@supertokens.io" }, + { name: "password", value: "Str0ngP@ssw0rd" }, + ]); - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); - await waitForUrl(page, "/auth/verify-email"); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + await waitForUrl(page, "/auth/verify-email"); - const latestURLWithToken = await getLatestURLWithToken(); - await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); - const title = await getTextByDataSupertokens(page, "headerTitle"); - assert.deepStrictEqual(title, "Email verification successful!"); + const title = await getTextByDataSupertokens(page, "headerTitle"); + assert.deepStrictEqual(title, "Email verification successful!"); - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await page.waitForSelector(".sessionInfo-user-id"); - await waitForUrl(page, "/dashboard"); + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await page.waitForSelector(".sessionInfo-user-id"); + await waitForUrl(page, "/dashboard"); - await Promise.all([ - page.waitForResponse( - (response) => response.url() === SEND_VERIFY_EMAIL_API && response.status() === 200 - ), - page.evaluate((url) => window.fetch(url), `${TEST_APPLICATION_SERVER_BASE_URL}/unverifyEmail`), - ]); - await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); + await page.evaluate((url) => window.fetch(url), `${TEST_APPLICATION_SERVER_BASE_URL}/unverifyEmail`); + await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); - await waitForUrl(page, "/auth/verify-email"); - }); - }); - - describe("Verify Email with token screen", function () { - beforeEach(async function () { - page = await browser.newPage(); - consoleLogs = []; - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); - } + await waitForUrl(page, "/auth/verify-email"); }); - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - }); - - it("Should show invalid token screen when token is invalid or expired", async function () { - await page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email?token=TOKEN&mode=REQUIRED`); - await waitForText(page, "[data-supertokens~=headerTitle]", "Verify your email address"); - await Promise.all([ - submitForm(page), - page.waitForResponse((response) => response.url() === VERIFY_EMAIL_API && response.status() === 200), - ]); - - await waitForText(page, "[data-supertokens~=headerTinyTitle]", "The email verification link has expired"); - // Click Continue should redirect to /auth when no session is present - await Promise.all([clickLinkWithRightArrow(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - - await waitForUrl(page, "/auth/"); - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", - "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", - ]); - }); - - it("Should ask for user interaction when token is valid with no session", async function () { - const latestURLWithToken = await getLatestURLWithToken(); - await page.goto(latestURLWithToken); - await waitForText(page, "[data-supertokens~=headerTitle]", "Verify your email address"); - await submitForm(page); - - await waitForText(page, "[data-supertokens~=headerTitle]", "Email verification successful!"); - - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await waitForUrl(page, "/auth/"); - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT EMAIL_VERIFIED_SUCCESSFUL", - "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", - ]); }); - it('Should show "Email Verification successful" screen when token is valid with an active session', async function () { - await toggleSignInSignUp(page); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe3@supertokens.io" }); - await signUp(page, fieldValues, postValues, "emailpassword"); + describe("Verify Email with token screen", function () { + it("Should show invalid token screen when token is invalid or expired", async function () { + consoleLogs = []; + await page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email?token=TOKEN&mode=REQUIRED`); + await waitForText(page, "[data-supertokens~=headerTitle]", "Verify your email address"); + await Promise.all([ + submitForm(page), + page.waitForResponse( + (response) => response.url() === VERIFY_EMAIL_API && response.status() === 200 + ), + ]); + + await waitForText( + page, + "[data-supertokens~=headerTinyTitle]", + "The email verification link has expired" + ); + // Click Continue should redirect to /auth when no session is present + await Promise.all([ + clickLinkWithRightArrow(page), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await waitForUrl(page, "/auth/"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", + "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", + ]); + }); - const latestURLWithToken = await getLatestURLWithToken(); - await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); - const title = await getTextByDataSupertokens(page, "headerTitle"); - assert.deepStrictEqual(title, "Email verification successful!"); - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await page.waitForSelector(".sessionInfo-user-id"); - await waitForUrl(page, "/dashboard"); - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS EMAIL_PASSWORD OVERRIDE DOES_EMAIL_EXIST", - "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_EXISTS", - "ST_LOGS EMAIL_PASSWORD OVERRIDE SIGN_UP", - "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_PASSWORD_SIGN_UP", - "ST_LOGS SESSION ON_HANDLE_EVENT SESSION_CREATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT SUCCESS", - "ST_LOGS EMAIL_VERIFICATION GET_REDIRECTION_URL VERIFY_EMAIL", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", - "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT EMAIL_VERIFIED_SUCCESSFUL", - "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL SUCCESS EMAIL_PASSWORD", - "ST_LOGS SESSION OVERRIDE GET_USER_ID", - ]); - }); + it("Should ask for user interaction when token is valid with no session", async function () { + // Sign Up, logout, clear logs + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues(); + await signUp(page, fieldValues, postValues, "emailpassword"); + await logoutFromEmailVerification(page); + await waitForUrl(page, "/auth/"); + consoleLogs = []; + + // Attempt verification without a session + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); + await waitForText(page, "[data-supertokens~=headerTitle]", "Verify your email address"); + await submitForm(page); + + await waitForText(page, "[data-supertokens~=headerTitle]", "Email verification successful!"); + + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await waitForUrl(page, "/auth/"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT EMAIL_VERIFIED_SUCCESSFUL", + "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL TO_AUTH", + ]); + }); - it("should successfully redirect after email verification without react-router-dom", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?router=no-router&rid=emailpassword&mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); + it('Should show "Email Verification successful" screen when token is valid with an active session', async function () { + consoleLogs = []; + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues(); + await signUp(page, fieldValues, postValues, "emailpassword"); + + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); + const title = await getTextByDataSupertokens(page, "headerTitle"); + assert.deepStrictEqual(title, "Email verification successful!"); + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await page.waitForSelector(".sessionInfo-user-id"); + await waitForUrl(page, "/dashboard"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS EMAIL_PASSWORD OVERRIDE DOES_EMAIL_EXIST", + "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_EXISTS", + "ST_LOGS EMAIL_PASSWORD OVERRIDE SIGN_UP", + "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_PASSWORD_SIGN_UP", + "ST_LOGS SESSION ON_HANDLE_EVENT SESSION_CREATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT SUCCESS", + "ST_LOGS EMAIL_VERIFICATION GET_REDIRECTION_URL VERIFY_EMAIL", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", + "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", + "ST_LOGS SESSION ON_HANDLE_EVENT ACCESS_TOKEN_PAYLOAD_UPDATED", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT EMAIL_VERIFIED_SUCCESSFUL", + "ST_LOGS SUPERTOKENS GET_REDIRECTION_URL SUCCESS EMAIL_PASSWORD", + "ST_LOGS SESSION OVERRIDE GET_USER_ID", + ]); + }); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe4@supertokens.io" }); - await signUp(page, fieldValues, postValues, "emailpassword"); + it("should successfully redirect after email verification without react-router-dom", async function () { + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?router=no-router&rid=emailpassword&mode=REQUIRED`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await toggleSignInSignUp(page); - const latestURLWithToken = await getLatestURLWithToken(); - await Promise.all([ - page.waitForNavigation({ waitUntil: "networkidle0" }), - page.goto(latestURLWithToken + "&router=no-router"), - ]); - const appHeader = await page.waitForSelector(".App > h1"); - const appHeaderText = await page.evaluate((e) => e.innerText, appHeader); - assert.strictEqual(appHeaderText, "Without Routing"); + const { fieldValues, postValues } = getDefaultSignUpFieldValues(); + await signUp(page, fieldValues, postValues, "emailpassword"); - const title = await getTextByDataSupertokens(page, "headerTitle"); - assert.deepStrictEqual(title, "Email verification successful!"); + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken + "&router=no-router"), + ]); + const appHeader = await page.waitForSelector(".App > h1"); + const appHeaderText = await page.evaluate((e) => e.innerText, appHeader); + assert.strictEqual(appHeaderText, "Without Routing"); - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await page.waitForSelector(".sessionInfo-user-id"); + const title = await getTextByDataSupertokens(page, "headerTitle"); + assert.deepStrictEqual(title, "Email verification successful!"); - await waitForUrl(page, "/dashboard"); - }); + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await page.waitForSelector(".sessionInfo-user-id"); - it("Should allow to verify an email without a valid session", async function () { - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - await page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email?token=TOKEN&mode=REQUIRED`); - await waitForText(page, "[data-supertokens~=headerTitle]", "Verify your email address"); - await Promise.all([ - submitForm(page), - page.waitForResponse((response) => response.url() === VERIFY_EMAIL_API && response.status() === 200), - ]); - await waitForUrl(page, "/auth/verify-email"); - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", - ]); - }); - }); - describe("Email Verified", function () { - beforeEach(async function () { - page = await browser.newPage(); - consoleLogs = []; - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); - } + await waitForUrl(page, "/dashboard"); }); - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - await page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email?mode=REQUIRED`); - }); - it("Should redirect to onSuccessfulRedirect when email is already verified", async function () { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await setInputValues(page, [ - { name: "email", value: "john.doe3@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - ]); - await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); - await page.waitForSelector(".sessionInfo-user-id"); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - // In this case we redirect to "/dashboard" (coming from the getRedirectURL config) - await waitForUrl(page, "/dashboard"); - }); - }); -}); - -describe("SuperTokens Email Verification server errors", function () { - let browser; - let page; - let consoleLogs; - - before(async function () { - browser = await setupBrowser(); - page = await browser.newPage(); - await page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`); - }); - - after(async function () { - await browser.close(); - }); - - describe("Verify Email with token screen", function () { - beforeEach(async function () { - page = await browser.newPage(); - consoleLogs = []; - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); - } + it("Should allow to verify an email without a valid session", async function () { + consoleLogs = []; + await page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email?token=TOKEN&mode=REQUIRED`); + await waitForText(page, "[data-supertokens~=headerTitle]", "Verify your email address"); + await Promise.all([ + submitForm(page), + page.waitForResponse( + (response) => response.url() === VERIFY_EMAIL_API && response.status() === 200 + ), + ]); + await waitForUrl(page, "/auth/verify-email"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", + ]); }); - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); }); - - it('Should show "Something went wrong" screen when API failure', async function () { - await page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email?token=TOKEN`); - await waitForText(page, "[data-supertokens~=headerTitle]", "Verify your email address"); - await Promise.all([ - submitForm(page), - page.waitForResponse((response) => response.url() === VERIFY_EMAIL_API && response.status() === 500), - ]); - await new Promise((r) => setTimeout(r, 50)); // Make sure to wait for status to update. - const verificationEmailErrorTitle = await getVerificationEmailErrorTitle(page); - assert.deepStrictEqual(verificationEmailErrorTitle, "!\nSomething went wrong"); - assert.deepStrictEqual(consoleLogs, [ - "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", - "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", - ]); + describe("Email Verified", function () { + it("Should redirect to onSuccessfulRedirect when email is already verified", async function () { + // Sign up + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues(); + await signUp(page, fieldValues, postValues, "emailpassword"); + // Verify Email and Logout + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle0" }), + page.goto(latestURLWithToken), + ]); + const title = await getTextByDataSupertokens(page, "headerTitle"); + assert.deepStrictEqual(title, "Email verification successful!"); + await submitForm(page); + // Make sure to wait for status to update. Flakes in headless. + await new Promise((r) => setTimeout(r, 500)); + const logoutButton = await getLogoutButton(page); + await Promise.all([logoutButton.click(), page.waitForNavigation({ waitUntil: "networkidle0" })]); + + // Proceed to test + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await setInputValues( + page, + fieldValues.filter((fv) => ["email", "password"].includes(fv.name)) + ); + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await page.waitForSelector(".sessionInfo-user-id"); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + // In this case we redirect to "/dashboard" (coming from the getRedirectURL config) + await waitForUrl(page, "/dashboard"); + }); }); }); -}); - -describe("SuperTokens Email Verification general errors", function () { - let browser; - let page; - let consoleLogs; - const generalErrorMessageString = "General Error"; - - before(async function () { - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - browser = await setupBrowser(); - page = await browser.newPage(); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - }); - - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - describe("Verify Email with token screen", function () { - beforeEach(async function () { - if (!(await isGeneralErrorSupported())) { - this.skip(); - } + describe("Server errors", function () { + describe("Verify Email with token screen", function () { + it('Should show "Something went wrong" screen when API failure', async function () { + await page.setRequestInterception(true); + page.on("request", (request) => { + if (request.url() === VERIFY_EMAIL_API && request.method() === "POST") { + request.respond({ + // Previous behavior was a result of core being shut down + // Emulating the same here + status: 500, + // body: "Error: No SuperTokens core available to query", + }); + } else { + request.continue(); + } + }); - page = await browser.newPage(); - consoleLogs = []; - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); - } + consoleLogs = []; + await page.goto(`${TEST_CLIENT_BASE_URL}/auth/verify-email?token=TOKEN`); + await waitForText(page, "[data-supertokens~=headerTitle]", "Verify your email address"); + await Promise.all([ + submitForm(page), + page.waitForResponse( + (response) => response.url() === VERIFY_EMAIL_API && response.status() === 500 + ), + ]); + await new Promise((r) => setTimeout(r, 50)); // Make sure to wait for status to update. + const verificationEmailErrorTitle = await getVerificationEmailErrorTitle(page); + assert.deepStrictEqual(verificationEmailErrorTitle, "!\nSomething went wrong"); + assert.deepStrictEqual(consoleLogs, [ + "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + "ST_LOGS EMAIL_VERIFICATION OVERRIDE VERIFY_EMAIL", + "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS VERIFY_EMAIL", + ]); }); - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - await page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`); - await page.evaluate(() => localStorage.removeItem("SHOW_GENERAL_ERROR")); - }); - - it('Should show "General Error" when API returns "GENERAL_ERROR"', async function () { - await toggleSignInSignUp(page); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe3@supertokens.io" }); - await signUp(page, fieldValues, postValues, "emailpassword"); - - const latestURLWithToken = await getLatestURLWithToken(); - await page.goto(latestURLWithToken); - - await setGeneralErrorToLocalStorage("EMAIL_VERIFICATION", "VERIFY_EMAIL", page); - await page.waitForResponse((response) => response.url() === VERIFY_EMAIL_API && response.status() === 200); - await new Promise((r) => setTimeout(r, 50)); // Make sure to wait for status to update. - const verificationEmailErrorTitle = await getVerificationEmailErrorTitle(page); - const verificationEmailErrorMessage = await getVerificationEmailErrorMessage(page); - assert.deepStrictEqual(verificationEmailErrorTitle, "!\nSomething went wrong"); - assert.deepStrictEqual(verificationEmailErrorMessage, "general error from API email verify"); }); }); - describe("Send verification email screen", function () { - beforeEach(async function () { - if (!(await isGeneralErrorSupported())) { - this.skip(); - } - - page = await browser.newPage(); - consoleLogs = []; - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); + describe("General errors", function () { + describe("Verify Email with token screen", function () { + beforeEach(async function () { + if (!(await isGeneralErrorSupported())) { + this.skip(); } + await page.evaluate(() => localStorage.removeItem("SHOW_GENERAL_ERROR")); }); - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - await page.evaluate(() => localStorage.removeItem("SHOW_GENERAL_ERROR")); - }); - - it('Should show "General Error" when API returns "GENERAL_ERROR" on resend', async function () { - await setGeneralErrorToLocalStorage("EMAIL_VERIFICATION", "SEND_VERIFY_EMAIL", page); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await toggleSignInSignUp(page); - await defaultSignUp(page); - await waitForUrl(page, "/auth/verify-email"); - await Promise.all([ - sendVerifyEmail(page), - page.waitForResponse( - (response) => response.url() === SEND_VERIFY_EMAIL_API && response.status() === 200 - ), - ]); - await new Promise((r) => setTimeout(r, 50)); // Make sure to wait for status to update. - const generalError = await getGeneralError(page); - assert.deepStrictEqual(generalError, "general error from API email verification code"); + it('Should show "General Error" when API returns "GENERAL_ERROR"', async function () { + await toggleSignInSignUp(page); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe3@supertokens.io" }); + await signUp(page, fieldValues, postValues, "emailpassword"); + + const latestURLWithToken = await getLatestURLWithToken(); + await page.goto(latestURLWithToken); + + await setGeneralErrorToLocalStorage("EMAIL_VERIFICATION", "VERIFY_EMAIL", page); + await page.waitForResponse( + (response) => response.url() === VERIFY_EMAIL_API && response.status() === 200 + ); + await new Promise((r) => setTimeout(r, 50)); // Make sure to wait for status to update. + const verificationEmailErrorTitle = await getVerificationEmailErrorTitle(page); + const verificationEmailErrorMessage = await getVerificationEmailErrorMessage(page); + assert.deepStrictEqual(verificationEmailErrorTitle, "!\nSomething went wrong"); + assert.deepStrictEqual(verificationEmailErrorMessage, "general error from API email verify"); + }); }); - }); -}); -describe("SuperTokens Email Verification isEmailVerified server error", function () { - let browser; - let page; - let consoleLogs; - - before(async function () { - // Start server. - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - browser = await setupBrowser(); - }); + describe("Send verification email screen", function () { + beforeEach(async function () { + if (!(await isGeneralErrorSupported())) { + this.skip(); + } - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - }); + await page.evaluate(() => localStorage.removeItem("SHOW_GENERAL_ERROR")); + }); - describe("Verify Email with token screen", function () { - beforeEach(async function () { - page = await browser.newPage(); - consoleLogs = []; - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); - } + it('Should show "General Error" when API returns "GENERAL_ERROR" on resend', async function () { + await setGeneralErrorToLocalStorage("EMAIL_VERIFICATION", "SEND_VERIFY_EMAIL", page); + + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + await toggleSignInSignUp(page); + await defaultSignUp(page); + await waitForUrl(page, "/auth/verify-email"); + await Promise.all([ + sendVerifyEmail(page), + page.waitForResponse( + (response) => response.url() === SEND_VERIFY_EMAIL_API && response.status() === 200 + ), + ]); + await new Promise((r) => setTimeout(r, 50)); // Make sure to wait for status to update. + const generalError = await getGeneralError(page); + assert.deepStrictEqual(generalError, "general error from API email verification code"); }); - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); }); - - // TODO: this test doesn't actually work cause it doesn't stop the server... strange - // it("Should ignore email verification when isEmailVerified server request fails", async function () { - // await Promise.all([ - // page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - // page.waitForNavigation({ waitUntil: "networkidle0" }), - // ]); - // await toggleSignInSignUp(page); - // await defaultSignUp(page); - - // // Stop server. - // await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - // method: "POST", - // }).catch(console.error); - - // // No redirection to /auth/veirfy-email if API call fails. - // await Promise.all([ - // page.goto(`${TEST_CLIENT_BASE_URL}/dashboard?mode=REQUIRED`), - // page.waitForNavigation({ waitUntil: "networkidle0" }), - // ]); - // await waitForUrl(page(pathname, "/dashboard"); - - // assert.deepStrictEqual(consoleLogs, [ - // "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - // "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - // "ST_LOGS EMAIL_PASSWORD OVERRIDE DOES_EMAIL_EXIST", - // "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_EXISTS", - // "ST_LOGS EMAIL_PASSWORD OVERRIDE SIGN_UP", - // "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_PASSWORD_SIGN_UP", - // "ST_LOGS SESSION ON_HANDLE_EVENT SESSION_CREATED", - // "ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT SUCCESS", - // "ST_LOGS EMAIL_VERIFICATION GET_REDIRECTION_URL VERIFY_EMAIL", - // "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", - // "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", - // "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", - // "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", - // "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", - // "ST_LOGS SESSION OVERRIDE GET_USER_ID", - // "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", - // "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", - // ]); - // }); - }); -}); - -describe("Email verification signOut errors", function () { - let browser; - let page; - before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await setupBrowser(); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - - beforeEach(async function () { - page = await browser.newPage(); - await clearBrowserCookiesWithoutAffectingConsole(page, []); - await page.evaluate(() => localStorage.removeItem("SHOW_GENERAL_ERROR")); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); + describe("isEmailVerified server error", function () { + describe("Verify Email with token screen", function () { + // TODO: this test doesn't actually work cause it doesn't stop the server... strange + // it("Should ignore email verification when isEmailVerified server request fails", async function () { + // await Promise.all([ + // page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), + // page.waitForNavigation({ waitUntil: "networkidle0" }), + // ]); + // await toggleSignInSignUp(page); + // await defaultSignUp(page); + // // Stop server. + // await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { + // method: "POST", + // }).catch(console.error); + // // No redirection to /auth/veirfy-email if API call fails. + // await Promise.all([ + // page.goto(`${TEST_CLIENT_BASE_URL}/dashboard?mode=REQUIRED`), + // page.waitForNavigation({ waitUntil: "networkidle0" }), + // ]); + // await waitForUrl(page(pathname, "/dashboard"); + // assert.deepStrictEqual(consoleLogs, [ + // "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + // "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + // "ST_LOGS EMAIL_PASSWORD OVERRIDE DOES_EMAIL_EXIST", + // "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_EXISTS", + // "ST_LOGS EMAIL_PASSWORD OVERRIDE SIGN_UP", + // "ST_LOGS EMAIL_PASSWORD PRE_API_HOOKS EMAIL_PASSWORD_SIGN_UP", + // "ST_LOGS SESSION ON_HANDLE_EVENT SESSION_CREATED", + // "ST_LOGS EMAIL_PASSWORD ON_HANDLE_EVENT SUCCESS", + // "ST_LOGS EMAIL_VERIFICATION GET_REDIRECTION_URL VERIFY_EMAIL", + // "ST_LOGS EMAIL_VERIFICATION OVERRIDE SEND_VERIFICATION_EMAIL", + // "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS SEND_VERIFY_EMAIL", + // "ST_LOGS EMAIL_VERIFICATION ON_HANDLE_EVENT VERIFY_EMAIL_SENT", + // "ST_LOGS SESSION OVERRIDE ADD_FETCH_INTERCEPTORS_AND_RETURN_MODIFIED_FETCH", + // "ST_LOGS SESSION OVERRIDE ADD_AXIOS_INTERCEPTORS", + // "ST_LOGS SESSION OVERRIDE GET_USER_ID", + // "ST_LOGS EMAIL_VERIFICATION OVERRIDE IS_EMAIL_VERIFIED", + // "ST_LOGS EMAIL_VERIFICATION PRE_API_HOOKS IS_EMAIL_VERIFIED", + // ]); + // }); + }); }); - it("Test that Something Went Wrong is displayed when signOut throws an error", async function () { - await toggleSignInSignUp(page); - await page.evaluate(() => localStorage.setItem("SHOW_GENERAL_ERROR", "SESSION SIGN_OUT")); + describe("signOut errors", function () { + it("Test that Something Went Wrong is displayed when signOut throws an error", async function () { + await toggleSignInSignUp(page); + await page.evaluate(() => localStorage.setItem("SHOW_GENERAL_ERROR", "SESSION SIGN_OUT")); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe2@supertokens.io" }); - await signUp(page, fieldValues, postValues, "emailpassword"); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: "john.doe2@supertokens.io" }); + await signUp(page, fieldValues, postValues, "emailpassword"); - await waitForUrl(page, "/auth/verify-email"); + await waitForUrl(page, "/auth/verify-email"); - await page.setRequestInterception(true); - const requestHandler = (request) => { - if (request.url() === SIGN_OUT_API && request.method() === "POST") { - return request.respond({ - status: 400, - headers: { - "access-control-allow-origin": TEST_CLIENT_BASE_URL, - "access-control-allow-credentials": "true", - }, - body: JSON.stringify({ - status: "BAD_INPUT", - }), - }); - } + await page.setRequestInterception(true); + const requestHandler = (request) => { + if (request.url() === SIGN_OUT_API && request.method() === "POST") { + return request.respond({ + status: 400, + headers: { + "access-control-allow-origin": TEST_CLIENT_BASE_URL, + "access-control-allow-credentials": "true", + }, + body: JSON.stringify({ + status: "BAD_INPUT", + }), + }); + } - return request.continue(); - }; - page.on("request", requestHandler); + return request.continue(); + }; + page.on("request", requestHandler); - await logoutFromEmailVerification(page); + await logoutFromEmailVerification(page); - await page.waitForResponse((response) => response.url() === SIGN_OUT_API && response.status() === 400); + await page.waitForResponse((response) => response.url() === SIGN_OUT_API && response.status() === 400); - page.off("request", requestHandler); - await page.setRequestInterception(false); + page.off("request", requestHandler); + await page.setRequestInterception(false); - const error = await waitForSTElement(page, "[data-supertokens~='generalError']"); - assert.strictEqual(await error.evaluate((e) => e.textContent), SOMETHING_WENT_WRONG_ERROR); + const error = await waitForSTElement(page, "[data-supertokens~='generalError']"); + assert.strictEqual(await error.evaluate((e) => e.textContent), SOMETHING_WENT_WRONG_ERROR); + }); }); -}); -describe("Email verification claim refresh with clock skew", function () { - let browser; - let page; - before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ + describe("Claim refresh with clock skew", function () { + it("should not go into an infinite loop during claim refresh with adjusted clock skew", async function () { + const coreUrl = await setupCoreApp({ coreConfig: { access_token_validity: 2 * 60 * 60, // 2 hours }, - }), - }).catch(console.error); - - browser = await setupBrowser(); - }); - - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - - beforeEach(async function () { - page = await browser.newPage(); - await clearBrowserCookiesWithoutAffectingConsole(page, []); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - }); - - it("should not go into an infinite loop during claim refresh with adjusted clock skew", async function () { - await toggleSignInSignUp(page); - - // Override Date.now() to simulate a clock skew of 1 hour - await page.evaluate(() => { - globalThis.originalNow = Date.now; - Date.now = function () { - return originalNow() + 60 * 60 * 1000; - }; - }); - - let claimRefreshCalledCount = 0; + }); + await setupST({ coreUrl }); + await toggleSignInSignUp(page); - await page.setRequestInterception(true); + // Override Date.now() to simulate a clock skew of 1 hour + await page.evaluate(() => { + globalThis.originalNow = Date.now; + Date.now = function () { + return originalNow() + 60 * 60 * 1000; + }; + }); - page.on("request", (req) => { - if (req.url() === `${TEST_APPLICATION_SERVER_BASE_URL}/auth/user/email/verify` && req.method() === "GET") { - // Simulate a failure after 5 claim refresh API calls to avoid an infinite loop - if (claimRefreshCalledCount >= 5) { - return req.respond({ status: 500, contentType: "text/plain", body: "Something went wrong" }); - } else { - claimRefreshCalledCount++; + let claimRefreshCalledCount = 0; + + await page.setRequestInterception(true); + + page.on("request", (req) => { + if ( + req.url() === `${TEST_APPLICATION_SERVER_BASE_URL}/auth/user/email/verify` && + req.method() === "GET" + ) { + // Simulate a failure after 5 claim refresh API calls to avoid an infinite loop + if (claimRefreshCalledCount >= 5) { + return req.respond({ status: 500, contentType: "text/plain", body: "Something went wrong" }); + } else { + claimRefreshCalledCount++; + } } - } - req.continue(); - }); + req.continue(); + }); - const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: await getTestEmail() }); - await signUp(page, fieldValues, postValues, "emailpassword"); - assert(claimRefreshCalledCount < 2); + const { fieldValues, postValues } = getDefaultSignUpFieldValues({ email: await getTestEmail() }); + await signUp(page, fieldValues, postValues, "emailpassword"); + assert(claimRefreshCalledCount < 2); + }); }); }); diff --git a/test/end-to-end/embed.test.js b/test/end-to-end/embed.test.js index 1ca49a76a..d278441ee 100644 --- a/test/end-to-end/embed.test.js +++ b/test/end-to-end/embed.test.js @@ -1,49 +1,52 @@ import assert from "assert"; -import puppeteer from "puppeteer"; -import fetch from "isomorphic-fetch"; -import { TEST_SERVER_BASE_URL } from "../constants"; import { AuthPage } from "./pages/AuthPage"; import { EmailVerificationPage } from "./pages/EmailVerificationPage"; import { ResetPasswordPage } from "./pages/ResetPasswordPage"; import { - backendBeforeEach, clearBrowserCookiesWithoutAffectingConsole, screenshotOnFailure, setupBrowser, + backendHook, + setupCoreApp, + setupST, + logoutFromEmailVerification, } from "../helpers"; describe("Embed components", async () => { let browser; let page; + let consoleLogs; before(async function () { + await backendHook("before"); browser = await setupBrowser(); - - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); }); - beforeEach(async () => { + beforeEach(async function () { + await backendHook("beforeEach"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); page = await browser.newPage(); - await clearBrowserCookiesWithoutAffectingConsole(page, []); + + consoleLogs = []; + page.on("console", (consoleObj) => { + const log = consoleObj.text(); + if (log.startsWith("ST_LOGS")) { + consoleLogs.push(log); + } + }); + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); }); - after(async function () { - await page.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - - await browser.close(); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); }); - afterEach(function () { - return screenshotOnFailure(this, browser); + after(async function () { + await browser?.close(); + await backendHook("after"); }); describe("EmailPassword SignInAndUp feature", () => { @@ -112,10 +115,10 @@ describe("Embed components", async () => { }); it("don't mount EmailVerification on /auth/verify-email if disableDefaultUI = true", async () => { - // First, sign in, as we signed up previously - const authPage = await AuthPage.navigate(page, { - ...testContext, - }); + // First, sign up and logout + const authPage = await AuthPage.navigate(page, testContext); + await authPage.signUp(); + await logoutFromEmailVerification(page); await authPage.signIn("john.doe@supertokens.io", "Str0ngP@ssw0rd"); diff --git a/test/end-to-end/generalerror.test.js b/test/end-to-end/generalerror.test.js index 228472f4a..fbf03564a 100644 --- a/test/end-to-end/generalerror.test.js +++ b/test/end-to-end/generalerror.test.js @@ -40,9 +40,11 @@ import { loginWithMockProvider, isGeneralErrorSupported, setGeneralErrorToLocalStorage, - backendBeforeEach, + backendHook, + setupCoreApp, waitForUrl, setupBrowser, + setupST, } from "../helpers"; import { @@ -57,48 +59,54 @@ import { SIGN_IN_UP_API, } from "../constants"; +let browser; +let page; +let consoleLogs; + describe("General error rendering", function () { before(async function () { const _isGeneralErrorSupported = await isGeneralErrorSupported(); if (!_isGeneralErrorSupported) { this.skip(); } - }); - describe("EmailPassword", function () { - getEmailPasswordTests("emailpassword", "EMAIL_PASSWORD"); + await backendHook("before"); + browser = await setupBrowser(); }); - describe("Email verification", function () { - let browser; - let page; - before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await setupBrowser(); + beforeEach(async function () { + await backendHook("beforeEach"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); + page = await browser.newPage(); + + consoleLogs = []; + page.on("console", (consoleObj) => { + const log = consoleObj.text(); + if (log.startsWith("ST_LOGS")) { + consoleLogs.push(log); + } }); + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); + }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); - afterEach(function () { - return screenshotOnFailure(this, browser); - }); + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + + describe("EmailPassword", function () { + getEmailPasswordTests("emailpassword", "EMAIL_PASSWORD"); + }); + describe("Email verification", function () { beforeEach(async function () { - page = await browser.newPage(); - await clearBrowserCookiesWithoutAffectingConsole(page, []); await page.evaluate(() => localStorage.removeItem("SHOW_GENERAL_ERROR")); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?mode=REQUIRED`), @@ -229,34 +237,7 @@ describe("General error rendering", function () { function getEmailPasswordTests(rid, ridForStorage) { describe("Email Password Tests", function () { - let browser; - let page; - before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await setupBrowser(); - }); - - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { - page = await browser.newPage(); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=${rid}`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -364,34 +345,7 @@ function getEmailPasswordTests(rid, ridForStorage) { function getThirdPartyTests(rid, ridForStorage) { describe("Third Party Tests", function () { - let browser; - let page; - before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await setupBrowser(); - }); - - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { - page = await browser.newPage(); await page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=${rid}`); await page.evaluate(() => localStorage.removeItem("SHOW_GENERAL_ERROR")); }); diff --git a/test/end-to-end/getRedirectionURL.test.js b/test/end-to-end/getRedirectionURL.test.js index 77efb1cf4..3d12f2b40 100644 --- a/test/end-to-end/getRedirectionURL.test.js +++ b/test/end-to-end/getRedirectionURL.test.js @@ -1,6 +1,4 @@ import assert from "assert"; -import fetch from "isomorphic-fetch"; -import puppeteer from "puppeteer"; import { toggleSignInSignUp, defaultSignUp, @@ -8,7 +6,6 @@ import { assertProviders, clickOnProviderButton, loginWithMockProvider, - setPasswordlessFlowType, waitForSTElement, getPasswordlessDevice, setInputValues, @@ -16,48 +13,45 @@ import { clearBrowserCookiesWithoutAffectingConsole, isPasswordlessSupported, isThirdPartyPasswordlessSupported, - backendBeforeEach, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import { - TEST_CLIENT_BASE_URL, - TEST_SERVER_BASE_URL, - SIGN_IN_UP_API, - TEST_APPLICATION_SERVER_BASE_URL, -} from "../constants"; +import { TEST_CLIENT_BASE_URL, SIGN_IN_UP_API } from "../constants"; describe("getRedirectionURL Tests", function () { - describe("Test that isNewRecipeUser is passed correctly", function () { - describe("Email Password Recipe", function () { - let browser; - let page; - before(async function () { - await backendBeforeEach(); + let browser; - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + before(async function () { + await backendHook("before"); + browser = await setupBrowser(); + }); - browser = await setupBrowser(); - }); + beforeEach(async function () { + await backendHook("beforeEach"); + }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); + }); - afterEach(function () { - return screenshotOnFailure(this, browser); - }); + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + + describe("Test that isNewRecipeUser is passed correctly", function () { + describe("Email Password Recipe", function () { + let page; beforeEach(async function () { page = await browser.newPage(); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); + await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=emailpassword`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -66,6 +60,10 @@ describe("getRedirectionURL Tests", function () { await clearBrowserCookiesWithoutAffectingConsole(page, []); }); + afterEach(async function () { + await page?.close(); + }); + it("Test that isNewRecipeUser is true when signing up", async function () { await toggleSignInSignUp(page); await defaultSignUp(page); @@ -75,39 +73,24 @@ describe("getRedirectionURL Tests", function () { }); describe("Third party recipe", function () { - let browser; let page; - before(async function () { - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await setupBrowser(); + before(async function () { + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); page = await browser.newPage(); await page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdparty`); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { await clearBrowserCookiesWithoutAffectingConsole(page, []); await page.evaluate(() => localStorage.removeItem("isNewUserCheck")); }); + after(async function () { + await page?.close(); + }); + it("Test that isNewRecipeUser works correctly", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), @@ -125,33 +108,14 @@ describe("getRedirectionURL Tests", function () { }); describe("Thirdpartyemailpassword recipe", function () { - let browser; let page; - before(async function () { - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await setupBrowser(); + before(async function () { + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); page = await browser.newPage(); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { await clearBrowserCookiesWithoutAffectingConsole(page, []); await Promise.all([ @@ -161,6 +125,10 @@ describe("getRedirectionURL Tests", function () { await page.evaluate(() => localStorage.removeItem("isNewUserCheck")); }); + after(async function () { + await page?.close(); + }); + it("Test that isNewRecipeUser is true when signing up with email", async function () { await toggleSignInSignUp(page); await defaultSignUp(page, "thirdpartyemailpassword"); @@ -181,34 +149,28 @@ describe("getRedirectionURL Tests", function () { }); describe("Passwordless recipe", function () { - let browser; - let page; const exampleEmail = "test@example.com"; - // Mocha calls cleanup functions even if the test block is skipped, this helps skipping the after block - let didSkip = false; + let page; before(async function () { let _isPasswordlessSupported = await isPasswordlessSupported(); if (!_isPasswordlessSupported) { - didSkip = true; this.skip(); return; } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - coreConfig: { - passwordless_code_lifetime: 4000, - passwordless_max_code_input_attempts: 3, - }, - }), - }).catch(console.error); + const coreUrl = await setupCoreApp({ + coreConfig: { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }, + }); + await setupST({ + coreUrl, + passwordlessFlowType: "USER_INPUT_CODE", + passwordlessContactMethod: "EMAIL", + }); - browser = await setupBrowser(); page = await browser.newPage(); await Promise.all([ page.goto( @@ -216,25 +178,6 @@ describe("getRedirectionURL Tests", function () { ), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - await setPasswordlessFlowType("EMAIL", "USER_INPUT_CODE"); - }); - - after(async function () { - // Dont cleanup if tests were skipped - if (didSkip) { - return; - } - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); }); beforeEach(async function () { @@ -242,6 +185,10 @@ describe("getRedirectionURL Tests", function () { await page.evaluate(() => localStorage.removeItem("isNewUserCheck")); }); + after(async function () { + await page?.close(); + }); + it("Test that isNewRecipeUser is passed correctly", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), @@ -265,34 +212,27 @@ describe("getRedirectionURL Tests", function () { }); describe("ThirdPartyPasswordless recipe", function () { - let browser; - let page; const exampleEmail = "test@example.com"; - // Mocha calls cleanup functions even if the test block is skipped, this helps skipping the after block - let didSkip = false; + let page; before(async function () { let _isThirdPartyPasswordlessSupported = await isThirdPartyPasswordlessSupported(); if (!_isThirdPartyPasswordlessSupported) { - didSkip = true; this.skip(); - return; } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - coreConfig: { - passwordless_code_lifetime: 4000, - passwordless_max_code_input_attempts: 3, - }, - }), - }).catch(console.error); + const coreUrl = await setupCoreApp({ + coreConfig: { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }, + }); + await setupST({ + coreUrl, + passwordlessFlowType: "USER_INPUT_CODE", + passwordlessContactMethod: "EMAIL", + }); - browser = await setupBrowser(); page = await browser.newPage(); await Promise.all([ page.goto( @@ -300,26 +240,6 @@ describe("getRedirectionURL Tests", function () { ), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - await setPasswordlessFlowType("EMAIL", "USER_INPUT_CODE"); - }); - - after(async function () { - // Dont cleanup if tests were skipped - if (didSkip) { - return; - } - - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); }); beforeEach(async function () { @@ -327,6 +247,10 @@ describe("getRedirectionURL Tests", function () { await page.evaluate(() => localStorage.removeItem("isNewUserCheck")); }); + after(async function () { + await page?.close(); + }); + it("Test that isNewRecipeUser is passed correctly", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), @@ -366,19 +290,13 @@ describe("getRedirectionURL Tests", function () { describe("No Redirection", function () { describe("Email Password Recipe", function () { - let browser; let page; before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await setupBrowser(); - + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); page = await browser.newPage(); + // We need to set the localStorage value before the page loads to ensure ST initialises with the correct value await page.evaluateOnNewDocument(() => { localStorage.setItem("disableRedirectionAfterSuccessfulSignInUp", "true"); @@ -389,17 +307,7 @@ describe("getRedirectionURL Tests", function () { }); after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); + await page?.close(); }); it("should not do any redirection after successful sign up", async function () { @@ -420,34 +328,27 @@ describe("getRedirectionURL Tests", function () { }); describe("Passwordless recipe", function () { - let browser; - let page; const exampleEmail = "test@example.com"; - // Mocha calls cleanup functions even if the test block is skipped, this helps skipping the after block - let didSkip = false; + let page; before(async function () { let _isPasswordlessSupported = await isPasswordlessSupported(); if (!_isPasswordlessSupported) { - didSkip = true; this.skip(); - return; } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - coreConfig: { - passwordless_code_lifetime: 4000, - passwordless_max_code_input_attempts: 3, - }, - }), - }).catch(console.error); + const coreUrl = await setupCoreApp({ + coreConfig: { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }, + }); + await setupST({ + coreUrl, + passwordlessFlowType: "USER_INPUT_CODE", + passwordlessContactMethod: "EMAIL", + }); - browser = await setupBrowser(); page = await browser.newPage(); // We need to set the localStorage value before the page loads to ensure ST initialises with the correct value await page.evaluateOnNewDocument(() => { @@ -461,25 +362,10 @@ describe("getRedirectionURL Tests", function () { ), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - return setPasswordlessFlowType("EMAIL", "USER_INPUT_CODE"); }); after(async function () { - // Dont cleanup if tests were skipped - if (didSkip) { - return; - } - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); + await page?.close(); }); it("should not do any redirection after successful sign up", async function () { @@ -510,65 +396,45 @@ describe("getRedirectionURL Tests", function () { }); describe("ThirdPartyPasswordless recipe: Magic Link", function () { - let browser; - let page; const exampleEmail = "test@example.com"; - // Mocha calls cleanup functions even if the test block is skipped, this helps skipping the after block - let didSkip = false; + let page; before(async function () { let _isThirdPartyPasswordlessSupported = await isThirdPartyPasswordlessSupported(); if (!_isThirdPartyPasswordlessSupported) { - didSkip = true; this.skip(); - return; } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - coreConfig: { - passwordless_code_lifetime: 4000, - passwordless_max_code_input_attempts: 3, - }, - }), - }).catch(console.error); + const coreUrl = await setupCoreApp({ + coreConfig: { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }, + }); + await setupST({ + coreUrl, + passwordlessFlowType: "MAGIC_LINK", + passwordlessContactMethod: "EMAIL", + }); - browser = await setupBrowser(); page = await browser.newPage(); + await clearBrowserCookiesWithoutAffectingConsole(page, []); // We need to set the localStorage value before the page loads to ensure ST initialises with the correct value await page.evaluateOnNewDocument(() => { localStorage.setItem("disableRedirectionAfterSuccessfulSignInUp", "true"); localStorage.removeItem("isNewUserCheck"); }); + await Promise.all([ page.goto( `${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartypasswordless&passwordlessContactMethodType=EMAIL` ), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); - await setPasswordlessFlowType("EMAIL", "MAGIC_LINK"); }); after(async function () { - // Dont cleanup if tests were skipped - if (didSkip) { - return; - } - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); + await page?.close(); }); it("should not do any redirection after successful sign up", async function () { @@ -588,7 +454,6 @@ describe("getRedirectionURL Tests", function () { const magicLink = device.codes[0].urlWithLinkCode; await page.goto(magicLink); - await page.waitForNetworkIdle(); const urlAfterSignUp = await page.url(); @@ -599,17 +464,10 @@ describe("getRedirectionURL Tests", function () { }); describe("ThirdParty Recipe", function () { - let browser; let page; - before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await setupBrowser(); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); page = await browser.newPage(); // We need to set the localStorage value before the page loads to ensure ST initialises with the correct value @@ -622,17 +480,7 @@ describe("getRedirectionURL Tests", function () { }); after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); + await page?.close(); }); it("should not do any redirection after successful sign up", async function () { diff --git a/test/end-to-end/mfa.chooserscreen.test.js b/test/end-to-end/mfa.chooserscreen.test.js index cb14c1d70..67aa139a4 100644 --- a/test/end-to-end/mfa.chooserscreen.test.js +++ b/test/end-to-end/mfa.chooserscreen.test.js @@ -18,46 +18,32 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, - setInputValues, - submitForm, waitForSTElement, screenshotOnFailure, - backendBeforeEach, - getTestEmail, - getPasswordlessDevice, - waitFor, getFactorChooserOptions, - setAccountLinkingConfig, isMFASupported, expectErrorThrown, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; -import { CREATE_CODE_API, CREATE_TOTP_DEVICE_API, MFA_INFO_API } from "../constants"; +import { MFA_INFO_API } from "../constants"; -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; -import { getTestPhoneNumber } from "../exampleTestHelpers"; +import { TEST_CLIENT_BASE_URL } from "../constants"; import { - setMFAInfo, - tryEmailPasswordSignUp, waitForDashboard, completeOTP, - setupOTP, - logout, tryEmailPasswordSignIn, chooseFactor, - tryPasswordlessSignInUp, - setupTOTP, completeTOTP, setupUserWithAllFactors, goToFactorChooser, waitForAccessDenied, - waitForLoadingScreen, - waitForBlockedScreen, } from "./mfa.helpers"; +import { randomUUID } from "crypto"; /* * Tests. @@ -66,48 +52,37 @@ describe("SuperTokens SignIn w/ MFA", function () { let browser; let page; let consoleLogs = []; - let skipped = false; + + const appConfig = { + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + mfaInfo: { + requirements: [{ oneOf: ["otp-email", "otp-phone"] }], + }, + }; before(async function () { if (!(await isMFASupported())) { - skipped = true; this.skip(); - return; } - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + backendHook("before"); + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); - await setAccountLinkingConfig(true, true, false); browser = await setupBrowser(); }); - after(async function () { - if (skipped) { - return; - } - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(async function () { - await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } - }); - beforeEach(async function () { + backendHook("beforeEach"); page = await browser.newPage(); + page.on("console", (consoleObj) => { const log = consoleObj.text(); // console.log(log); @@ -123,9 +98,21 @@ describe("SuperTokens SignIn w/ MFA", function () { await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true")); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + backendHook("after"); + }); + describe("chooser screen", () => { let email, phoneNumber; let totpSecret; + before(async () => { page = await browser.newPage(); ({ email, phoneNumber, totpSecret } = await setupUserWithAllFactors(page)); @@ -138,8 +125,11 @@ describe("SuperTokens SignIn w/ MFA", function () { window.localStorage.setItem("clientRecipeListForDynamicLogin", JSON.stringify(["emailpassword"])); }); - await setMFAInfo({ - requirements: [{ oneOf: ["otp-email", "otp-phone", "totp"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["otp-email", "otp-phone", "totp"] }], + }, }); await tryEmailPasswordSignIn(page, email); @@ -154,11 +144,14 @@ describe("SuperTokens SignIn w/ MFA", function () { window.localStorage.setItem("clientRecipeListForDynamicLogin", JSON.stringify(["emailpassword"])); }); - await setMFAInfo({ - requirements: [{ oneOf: ["otp-email", "otp-phone", "totp"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["otp-email", "otp-phone", "totp"] }], - alreadySetup: ["totp"], - allowedToSetup: [], + alreadySetup: ["totp"], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -167,8 +160,11 @@ describe("SuperTokens SignIn w/ MFA", function () { await waitForDashboard(page); }); it("should redirect to the factor screen during sign in if only one factor is available (limited by next array)", async () => { - await setMFAInfo({ - requirements: [{ oneOf: ["totp"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["totp"] }], + }, }); await tryEmailPasswordSignIn(page, email); @@ -178,8 +174,11 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show all factors the user can complete or set up in the next array", async () => { - await setMFAInfo({ - requirements: [{ oneOf: ["totp", "otp-email"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["totp", "otp-email"] }], + }, }); await tryEmailPasswordSignIn(page, email); @@ -189,10 +188,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show all factors the user can complete or set up if the next array is empty", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: ["otp-phone", "otp-email"], - allowedToSetup: ["totp"], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: ["otp-phone", "otp-email"], + allowedToSetup: ["totp"], + }, }); await tryEmailPasswordSignIn(page, email); @@ -203,10 +205,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should throw error if there are no available options during sign in", async () => { - await setMFAInfo({ - requirements: ["otp-phone"], - alreadySetup: ["otp-email"], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: ["otp-phone"], + alreadySetup: ["otp-email"], + allowedToSetup: [], + }, }); await expectErrorThrown(page, () => tryEmailPasswordSignIn(page, email)); @@ -214,10 +219,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show access denied if there are no available options after sign in", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -227,8 +235,11 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a back link only if visited after sign in", async () => { - await setMFAInfo({ - requirements: [{ oneOf: ["otp-email", "otp-phone"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["otp-email", "otp-phone"] }], + }, }); await tryEmailPasswordSignIn(page, email); @@ -244,8 +255,11 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a logout link", async () => { - await setMFAInfo({ - requirements: [{ oneOf: ["otp-email", "otp-phone"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["otp-email", "otp-phone"] }], + }, }); await tryEmailPasswordSignIn(page, email); @@ -261,10 +275,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should handle MFA info API failures gracefully", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: ["otp-phone", "otp-email"], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: ["otp-phone", "otp-email"], + allowedToSetup: [], + }, }); await page.setRequestInterception(true); diff --git a/test/end-to-end/mfa.default_reqs.test.js b/test/end-to-end/mfa.default_reqs.test.js index 04e150bf8..662ed555a 100644 --- a/test/end-to-end/mfa.default_reqs.test.js +++ b/test/end-to-end/mfa.default_reqs.test.js @@ -16,46 +16,30 @@ /* * Imports */ - import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, - setInputValues, - submitForm, - waitForSTElement, screenshotOnFailure, - backendBeforeEach, + backendHook, getTestEmail, - getPasswordlessDevice, - waitFor, getFactorChooserOptions, - setAccountLinkingConfig, isMFASupported, setupBrowser, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; -import { CREATE_CODE_API, CREATE_TOTP_DEVICE_API, MFA_INFO_API, TEST_APPLICATION_SERVER_BASE_URL } from "../constants"; - -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; +import { TEST_CLIENT_BASE_URL } from "../constants"; import { getTestPhoneNumber } from "../exampleTestHelpers"; import { - setMFAInfo, tryEmailPasswordSignUp, waitForDashboard, completeOTP, setupOTP, logout, tryEmailPasswordSignIn, - chooseFactor, - tryPasswordlessSignInUp, setupTOTP, completeTOTP, - setupUserWithAllFactors, goToFactorChooser, - waitForAccessDenied, - waitForLoadingScreen, - waitForBlockedScreen, addToRequiredSecondaryFactorsForUser, } from "./mfa.helpers"; @@ -66,47 +50,28 @@ describe("SuperTokens SignIn w/ MFA", function () { let browser; let page; let consoleLogs = []; - let skipped = false; + + const appConfig = { + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }; before(async function () { if (!(await isMFASupported())) { - skipped = true; this.skip(); - return; } - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - await setAccountLinkingConfig(true, true, false); + await backendHook("before"); browser = await setupBrowser(); }); - after(async function () { - if (skipped) { - return; - } - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(async function () { - await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } - }); - beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); page.on("console", (consoleObj) => { const log = consoleObj.text(); @@ -123,10 +88,23 @@ describe("SuperTokens SignIn w/ MFA", function () { await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true")); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("default requirements", () => { let email, phoneNumber; + beforeEach(async () => { - await setMFAInfo({}); + const coreUrl = await setupCoreApp(); + await setupST({ ...appConfig, coreUrl }); const setupPage = await browser.newPage(); email = await getTestEmail(); diff --git a/test/end-to-end/mfa.factorscreen.otp.test.js b/test/end-to-end/mfa.factorscreen.otp.test.js index 6632126ab..d0b737de9 100644 --- a/test/end-to-end/mfa.factorscreen.otp.test.js +++ b/test/end-to-end/mfa.factorscreen.otp.test.js @@ -18,52 +18,33 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, setInputValues, submitForm, waitForSTElement, screenshotOnFailure, - backendBeforeEach, getTestEmail, - getPasswordlessDevice, - waitFor, - getFactorChooserOptions, getGeneralError, isMFASupported, - setAccountLinkingConfig, waitForUrl, setupBrowser, + setupCoreApp, + backendHook, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; -import { - CONSUME_CODE_API, - CREATE_CODE_API, - CREATE_TOTP_DEVICE_API, - MFA_INFO_API, - SOMETHING_WENT_WRONG_ERROR, -} from "../constants"; - -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; +import { CONSUME_CODE_API, CREATE_CODE_API, MFA_INFO_API, SOMETHING_WENT_WRONG_ERROR } from "../constants"; + +import { TEST_CLIENT_BASE_URL } from "../constants"; import { getTestPhoneNumber } from "../exampleTestHelpers"; import { - setMFAInfo, tryEmailPasswordSignUp, waitForDashboard, completeOTP, - setupOTP, - logout, tryEmailPasswordSignIn, chooseFactor, - tryPasswordlessSignInUp, - setupTOTP, - completeTOTP, - setupUserWithAllFactors, - goToFactorChooser, waitForAccessDenied, waitForLoadingScreen, - waitForBlockedScreen, } from "./mfa.helpers"; /* @@ -73,47 +54,31 @@ describe("SuperTokens SignIn w/ MFA", function () { let browser; let page; let consoleLogs = []; - let skipped = false; + + const appConfig = { + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }; before(async function () { if (!(await isMFASupported())) { - skipped = true; this.skip(); - return; } - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - await setAccountLinkingConfig(true, true, false); + await backendHook("before"); browser = await setupBrowser(); - }); - - after(async function () { - if (skipped) { - return; - } - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(async function () { - await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); }); beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); page.on("console", (consoleObj) => { const log = consoleObj.text(); @@ -130,6 +95,17 @@ describe("SuperTokens SignIn w/ MFA", function () { await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true")); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("factor screens", () => { describe("otp", () => { describe("otp-phone", () => { @@ -143,7 +119,7 @@ describe("SuperTokens SignIn w/ MFA", function () { function getOTPTests(contactMethod, factorId) { let email, phoneNumber; before(async () => { - await setMFAInfo({}); + await setupST(appConfig); page = await browser.newPage(); email = await getTestEmail(factorId); @@ -169,10 +145,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should respect redirectToPath", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -199,10 +178,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show general error if the app navigates to the setup page but the user is not allowed to set up the factor", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -226,10 +208,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show loading screen", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -260,10 +245,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should handle MFA info API failures gracefully", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -299,10 +287,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should handle createCode failures gracefully", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await page.setRequestInterception(true); @@ -333,10 +324,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should handle consumeCode restart flow error", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [factorId], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [factorId], + allowedToSetup: [factorId], + }, }); await page.setRequestInterception(true); @@ -370,11 +364,14 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should handle consumeCode restart flow error when setting up factor", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [], - allowedToSetup: [factorId], - noContacts: true, + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [], + allowedToSetup: [factorId], + noContacts: true, + }, }); await page.setRequestInterception(true); @@ -416,11 +413,14 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should enable you to change the contact info during setup (w/ contact form)", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [], - allowedToSetup: [factorId], - noContacts: true, + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [], + allowedToSetup: [factorId], + noContacts: true, + }, }); await tryEmailPasswordSignIn(page, email); @@ -448,10 +448,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a link redirecting back if visited after sign in without stepUp param", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -464,10 +467,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a link redirecting back if visited after sign in - force setup", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -481,10 +487,13 @@ describe("SuperTokens SignIn w/ MFA", function () { await waitForDashboard(page); }); it("should show a link redirecting back if visited after sign in - setup in stepUp", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -499,10 +508,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a link redirecting back if visited after sign in - verification in stepUp", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -517,11 +529,14 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a back button redirecting to the chooser screen if other options are available during sign in - setup", async () => { - await setMFAInfo({ - requirements: [{ oneOf: [factorId, "totp"] }], - alreadySetup: ["totp"], - allowedToSetup: [factorId], - noContacts: true, + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: [factorId, "totp"] }], + alreadySetup: ["totp"], + allowedToSetup: [factorId], + noContacts: true, + }, }); await tryEmailPasswordSignIn(page, email); @@ -537,10 +552,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a back button redirecting to the chooser screen if other options are available during sign in - verification", async () => { - await setMFAInfo({ - requirements: [{ oneOf: [factorId, "totp"] }], - alreadySetup: [factorId, "totp"], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: [factorId, "totp"] }], + alreadySetup: [factorId, "totp"], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -555,11 +573,14 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a logout link - setup", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [], - allowedToSetup: [factorId], - noContacts: true, + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [], + allowedToSetup: [factorId], + noContacts: true, + }, }); await tryEmailPasswordSignIn(page, email); @@ -574,10 +595,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a logout link - verification", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); diff --git a/test/end-to-end/mfa.factorscreen.totp.test.js b/test/end-to-end/mfa.factorscreen.totp.test.js index aafdc6398..9654304a1 100644 --- a/test/end-to-end/mfa.factorscreen.totp.test.js +++ b/test/end-to-end/mfa.factorscreen.totp.test.js @@ -18,43 +18,30 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, - setInputValues, - submitForm, waitForSTElement, screenshotOnFailure, - backendBeforeEach, getTestEmail, - getPasswordlessDevice, waitFor, - getFactorChooserOptions, isMFASupported, - setAccountLinkingConfig, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; -import { CREATE_CODE_API, CREATE_TOTP_DEVICE_API, MFA_INFO_API } from "../constants"; +import { CREATE_TOTP_DEVICE_API, MFA_INFO_API } from "../constants"; -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; -import { getTestPhoneNumber } from "../exampleTestHelpers"; +import { TEST_CLIENT_BASE_URL } from "../constants"; import { - setMFAInfo, tryEmailPasswordSignUp, waitForDashboard, - completeOTP, - setupOTP, - logout, tryEmailPasswordSignIn, chooseFactor, - tryPasswordlessSignInUp, setupTOTP, getTOTPSecret, completeTOTP, - setupUserWithAllFactors, - goToFactorChooser, waitForAccessDenied, waitForLoadingScreen, waitForBlockedScreen, @@ -67,47 +54,30 @@ describe("SuperTokens SignIn w/ MFA", function () { let browser; let page; let consoleLogs = []; - let skipped = false; + + const appConfig = { + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }; before(async function () { if (!(await isMFASupported())) { - skipped = true; this.skip(); - return; } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - await setAccountLinkingConfig(true, true, false); - + await backendHook("before"); browser = await setupBrowser(); - }); - - after(async function () { - if (skipped) { - return; - } - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(async function () { - await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); }); beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); page.on("console", (consoleObj) => { const log = consoleObj.text(); @@ -124,14 +94,28 @@ describe("SuperTokens SignIn w/ MFA", function () { await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true")); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("factor screens", () => { describe("totp", () => { const factorId = "totp"; let email, phoneNumber; before(async () => { - await setMFAInfo({ - allowedToSetup: ["totp"], + await setupST({ + ...appConfig, + mfaInfo: { + allowedToSetup: ["totp"], + }, }); page = await browser.newPage(); @@ -156,10 +140,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should respect redirectToPath", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -179,10 +166,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show access denied if the app navigates to the setup page but the user it is not allowed to set up the factor", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -196,10 +186,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show access denied if setup is not allowed but the factor is not set up", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -212,10 +205,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show loading screen", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -243,10 +239,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show blocked screen after too many retries", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -257,10 +256,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should handle mfa info api failures gracefully", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -296,10 +298,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should handle createDevice failures gracefully", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [], + allowedToSetup: [factorId], + }, }); await page.setRequestInterception(true); @@ -330,10 +335,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should redirect back if visited after sign in without stepUp param", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -346,10 +354,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a link redirecting back if visited after sign in - force setup", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -364,10 +375,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a link redirecting back if visited after sign in - setup in stepUp", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -382,10 +396,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a link redirecting back if visited after sign in - verification in stepUp", async () => { - await setMFAInfo({ - requirements: [], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -400,10 +417,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a back button redirecting to the chooser screen if other options are available during sign in - setup", async () => { - await setMFAInfo({ - requirements: [{ oneOf: [factorId, "otp-email"] }], - alreadySetup: ["otp-email"], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: [factorId, "otp-email"] }], + alreadySetup: ["otp-email"], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -419,10 +439,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a back button redirecting to the chooser screen if other options are available during sign in - verification", async () => { - await setMFAInfo({ - requirements: [{ oneOf: [factorId, "otp-email"] }], - alreadySetup: [factorId, "otp-email"], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: [factorId, "otp-email"] }], + alreadySetup: [factorId, "otp-email"], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); @@ -437,10 +460,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a logout link - setup", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [], - allowedToSetup: [factorId], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [], + allowedToSetup: [factorId], + }, }); await tryEmailPasswordSignIn(page, email); @@ -456,10 +482,13 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should show a logout link - verify", async () => { - await setMFAInfo({ - requirements: [factorId], - alreadySetup: [factorId], - allowedToSetup: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [factorId], + alreadySetup: [factorId], + allowedToSetup: [], + }, }); await tryEmailPasswordSignIn(page, email); diff --git a/test/end-to-end/mfa.firstFactors.test.js b/test/end-to-end/mfa.firstFactors.test.js index c3cdd2fcf..7ce852c13 100644 --- a/test/end-to-end/mfa.firstFactors.test.js +++ b/test/end-to-end/mfa.firstFactors.test.js @@ -18,21 +18,19 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, waitForSTElement, screenshotOnFailure, - backendBeforeEach, waitFor, submitForm, isMFASupported, - setAccountLinkingConfig, setupBrowser, + setupCoreApp, + setupST, + backendHook, } from "../helpers"; -import fetch from "isomorphic-fetch"; - -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; +import { TEST_CLIENT_BASE_URL } from "../constants"; import { getTestPhoneNumber } from "../exampleTestHelpers"; /* @@ -42,48 +40,31 @@ describe("SuperTokens MFA firstFactors support", function () { let browser; let page; let consoleLogs = []; - let skipped = false; + + const appConfig = { + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }; before(async function () { if (!(await isMFASupported())) { - skipped = true; this.skip(); - return; } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - await setAccountLinkingConfig(true, true, false); + await backendHook("before"); browser = await setupBrowser(); - }); - after(async function () { - if (skipped) { - return; - } - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(async function () { - await screenshotOnFailure(this, browser); - if (page) { - page.evaluate(() => window.localStorage.removeItem("firstFactors")); - await page.close(); - } + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); }); beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); page.on("console", (consoleObj) => { const log = consoleObj.text(); @@ -99,6 +80,18 @@ describe("SuperTokens MFA firstFactors support", function () { }); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + page?.evaluate(() => window.localStorage.removeItem("firstFactors")); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("with firstFactors set on the client", () => { it("should display pwless w/ phone for [otp-phone]", async () => { await page.evaluate(() => { diff --git a/test/end-to-end/mfa.helpers.js b/test/end-to-end/mfa.helpers.js index 03a9fc5d1..f3d59dadb 100644 --- a/test/end-to-end/mfa.helpers.js +++ b/test/end-to-end/mfa.helpers.js @@ -23,10 +23,6 @@ export async function setupUserWithAllFactors(page) { await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true")); await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); - await setMFAInfo({ - requirements: [{ oneOf: ["otp-email", "otp-phone"] }], - }); - await tryEmailPasswordSignUp(page, email); await waitForSTElement(page, "[data-supertokens~='sendVerifyEmailIcon']"); @@ -51,14 +47,6 @@ export async function setupUserWithAllFactors(page) { const totpSecret = await setupTOTP(page); return { email, phoneNumber, totpSecret }; } -export async function setMFAInfo(mfaInfo) { - let resp = await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/setMFAInfo`, { - method: "POST", - headers: new Headers([["content-type", "application/json"]]), - body: JSON.stringify(mfaInfo), - }); - assert.strictEqual(resp.status, 200); -} export async function addToRequiredSecondaryFactorsForUser(page, factorId) { await page.evaluate( diff --git a/test/end-to-end/mfa.requirement_handling.test.js b/test/end-to-end/mfa.requirement_handling.test.js index e170f1e45..b401d7795 100644 --- a/test/end-to-end/mfa.requirement_handling.test.js +++ b/test/end-to-end/mfa.requirement_handling.test.js @@ -18,44 +18,29 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, - setInputValues, - submitForm, - waitForSTElement, screenshotOnFailure, - backendBeforeEach, getTestEmail, - getPasswordlessDevice, - waitFor, getFactorChooserOptions, - setAccountLinkingConfig, isMFASupported, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; -import { CREATE_CODE_API, CREATE_TOTP_DEVICE_API, MFA_INFO_API } from "../constants"; - -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; +import { TEST_CLIENT_BASE_URL } from "../constants"; import { getTestPhoneNumber } from "../exampleTestHelpers"; import { - setMFAInfo, tryEmailPasswordSignUp, waitForDashboard, completeOTP, setupOTP, - logout, tryEmailPasswordSignIn, chooseFactor, - tryPasswordlessSignInUp, setupTOTP, completeTOTP, - setupUserWithAllFactors, goToFactorChooser, - waitForAccessDenied, - waitForLoadingScreen, - waitForBlockedScreen, } from "./mfa.helpers"; /* @@ -65,46 +50,31 @@ describe("SuperTokens SignIn w/ MFA", function () { let browser; let page; let consoleLogs = []; - let skipped; + + const appConfig = { + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }; before(async function () { if (!(await isMFASupported())) { - skipped = true; this.skip(); - return; } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - + await backendHook("before"); browser = await setupBrowser(); - }); - - after(async function () { - if (skipped) { - return; - } - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - afterEach(async function () { - await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); }); beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); page.on("console", (consoleObj) => { const log = consoleObj.text(); @@ -121,18 +91,26 @@ describe("SuperTokens SignIn w/ MFA", function () { await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true")); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("requirement handling", () => { let email, phoneNumber; let secret; before(async () => { - await setMFAInfo({}); page = await browser.newPage(); email = await getTestEmail(); phoneNumber = getTestPhoneNumber(); - await setMFAInfo({}); - await setAccountLinkingConfig(true, true, false); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth/?rid=emailpassword`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -157,9 +135,10 @@ describe("SuperTokens SignIn w/ MFA", function () { describe("multistep requirement list", () => { it("multistep requirements should happen in order (allOfInAnyOrder -> oneOf)", async () => { - await setMFAInfo({ + appConfig.mfaInfo = { requirements: [{ allOfInAnyOrder: ["otp-phone", "totp"] }, { oneOf: ["otp-email"] }], - }); + }; + await setupST(appConfig); await tryEmailPasswordSignIn(page, email); const factors1 = await getFactorChooserOptions(page); @@ -172,9 +151,10 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("multistep requirements should happen in order (oneOf -> allOfInAnyOrder)", async () => { - await setMFAInfo({ + appConfig.mfaInfo = { requirements: [{ oneOf: ["otp-phone", "totp"] }, { allOfInAnyOrder: ["totp", "otp-email"] }], - }); + }; + await setupST(appConfig); await tryEmailPasswordSignIn(page, email); const factors1 = await getFactorChooserOptions(page); @@ -189,9 +169,10 @@ describe("SuperTokens SignIn w/ MFA", function () { await waitForDashboard(page); }); it("string requirements strictly set the order of the factor screens", async () => { - await setMFAInfo({ + appConfig.mfaInfo = { requirements: ["otp-phone", "totp", "otp-email"], - }); + }; + await setupST(appConfig); await tryEmailPasswordSignIn(page, email); await completeOTP(page, "PHONE"); @@ -203,9 +184,10 @@ describe("SuperTokens SignIn w/ MFA", function () { describe("allOfInAnyOrder", () => { it("should pass if all requirements are complete", async () => { - await setMFAInfo({ + appConfig.mfaInfo = { requirements: [{ allOfInAnyOrder: ["otp-phone", "totp", "otp-email"] }], - }); + }; + await setupST(appConfig); await tryEmailPasswordSignIn(page, email); const factors1 = await getFactorChooserOptions(page); @@ -222,9 +204,10 @@ describe("SuperTokens SignIn w/ MFA", function () { await waitForDashboard(page); }); it("should pass if the array is empty", async () => { - await setMFAInfo({ + appConfig.mfaInfo = { requirements: [{ allOfInAnyOrder: [] }], - }); + }; + await setupST(appConfig); await tryEmailPasswordSignIn(page, email); await waitForDashboard(page); @@ -233,9 +216,10 @@ describe("SuperTokens SignIn w/ MFA", function () { describe("oneOf", () => { it("should pass if one of the requirements are complete", async () => { - await setMFAInfo({ + appConfig.mfaInfo = { requirements: [{ oneOf: ["otp-phone", "totp", "otp-email"] }], - }); + }; + await setupST(appConfig); await tryEmailPasswordSignIn(page, email); const factors1 = await getFactorChooserOptions(page); @@ -246,9 +230,10 @@ describe("SuperTokens SignIn w/ MFA", function () { await waitForDashboard(page); }); it("should pass if the array is empty", async () => { - await setMFAInfo({ + appConfig.mfaInfo = { requirements: [{ oneOf: [] }], - }); + }; + await setupST(appConfig); await tryEmailPasswordSignIn(page, email); await waitForDashboard(page); diff --git a/test/end-to-end/mfa.signin.test.js b/test/end-to-end/mfa.signin.test.js index 364f234a5..020918c6e 100644 --- a/test/end-to-end/mfa.signin.test.js +++ b/test/end-to-end/mfa.signin.test.js @@ -17,31 +17,25 @@ * Imports */ -import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, setInputValues, submitForm, waitForSTElement, screenshotOnFailure, - backendBeforeEach, getTestEmail, getPasswordlessDevice, waitFor, isMFASupported, - setAccountLinkingConfig, expectErrorThrown, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; -import { CREATE_CODE_API, CREATE_TOTP_DEVICE_API, MFA_INFO_API } from "../constants"; - -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; import { getTestPhoneNumber } from "../exampleTestHelpers"; import { - setMFAInfo, tryEmailPasswordSignUp, waitForDashboard, completeOTP, @@ -52,11 +46,7 @@ import { tryPasswordlessSignInUp, setupTOTP, completeTOTP, - setupUserWithAllFactors, - goToFactorChooser, waitForAccessDenied, - waitForLoadingScreen, - waitForBlockedScreen, } from "./mfa.helpers"; /* @@ -66,55 +56,40 @@ describe("SuperTokens SignIn w/ MFA", function () { let browser; let page; let consoleLogs = []; - let skipped = false; + + const appConfig = { + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: false, + }, + }, + }; before(async function () { if (!(await isMFASupported())) { - skipped = true; this.skip(); - return; } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - await setAccountLinkingConfig(true, true, false); - + await backendHook("before"); browser = await setupBrowser(); - }); - after(async function () { - if (skipped) { - return; - } - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(async function () { - await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); }); beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); page.on("console", (consoleObj) => { const log = consoleObj.text(); + // console.log(log); if (log.startsWith("ST_LOGS")) { consoleLogs.push(log); } }); + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); await page.evaluate(() => window.localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); @@ -123,11 +98,26 @@ describe("SuperTokens SignIn w/ MFA", function () { await page.evaluate(() => window.localStorage.setItem("enableAllRecipes", "true")); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + page?.evaluate(() => window.localStorage.removeItem("firstFactors")); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + it("sign in with email-otp (auto-setup)", async function () { const email = await getTestEmail(); - await setMFAInfo({ - requirements: ["otp-email"], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: ["otp-email"], + }, }); await tryEmailPasswordSignUp(page, email); @@ -149,8 +139,11 @@ describe("SuperTokens SignIn w/ MFA", function () { const email = await getTestEmail(); const phoneNumber = getTestPhoneNumber(); - await setMFAInfo({ - requirements: [{ oneOf: ["otp-email", "otp-phone"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["otp-email", "otp-phone"] }], + }, }); await tryEmailPasswordSignUp(page, email); @@ -170,8 +163,11 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("set up otp-email and sign-in", async function () { - await setMFAInfo({ - requirements: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + }, }); const email = await getTestEmail(); const phoneNumber = getTestPhoneNumber(); @@ -183,8 +179,11 @@ describe("SuperTokens SignIn w/ MFA", function () { await logout(page); - await setMFAInfo({ - requirements: [{ oneOf: ["otp-email"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["otp-email"] }], + }, }); await tryPasswordlessSignInUp(page, phoneNumber, undefined, true); @@ -204,13 +203,19 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("set up totp and sign-in", async function () { - await setMFAInfo({ - requirements: [], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [], + }, }); const email = await getTestEmail(); - await setMFAInfo({ - requirements: [{ oneOf: ["otp-email", "totp"] }], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: [{ oneOf: ["otp-email", "totp"] }], + }, }); await tryEmailPasswordSignUp(page, email); @@ -240,9 +245,12 @@ describe("SuperTokens SignIn w/ MFA", function () { it("should show access denied if the only next option is an unknown factor id", async () => { const email = await getTestEmail(); - await setMFAInfo({ - requirements: ["unknown"], - alreadySetup: ["unknown"], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: ["unknown"], + alreadySetup: ["unknown"], + }, }); await tryEmailPasswordSignUp(page, email); @@ -250,8 +258,11 @@ describe("SuperTokens SignIn w/ MFA", function () { }); it("should throw error if there are no valid next options", async () => { - await setMFAInfo({ - requirements: ["unknown"], + await setupST({ + ...appConfig, + mfaInfo: { + requirements: ["unknown"], + }, }); const email = await getTestEmail(); diff --git a/test/end-to-end/multitenancy.dynamic_login_methods.test.js b/test/end-to-end/multitenancy.dynamic_login_methods.test.js index 303235eb9..37a458961 100644 --- a/test/end-to-end/multitenancy.dynamic_login_methods.test.js +++ b/test/end-to-end/multitenancy.dynamic_login_methods.test.js @@ -18,8 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; -import fetch from "isomorphic-fetch"; import { waitForSTElement, screenshotOnFailure, @@ -31,28 +29,27 @@ import { getSignInOrSignUpSwitchLink, setInputValues, submitForm, - loginWithGoogle, clearBrowserCookiesWithoutAffectingConsole, clickOnProviderButton, loginWithMockProvider, isMultitenancySupported, isMultitenancyManagementEndpointsSupported, setupTenant, - backendBeforeEach, getTextByDataSupertokens, setupBrowser, loginWithAuth0, + setupCoreApp, + setupST, + backendHook, } from "../helpers"; import { TEST_CLIENT_BASE_URL, DEFAULT_WEBSITE_BASE_PATH, - TEST_SERVER_BASE_URL, SIGN_IN_UP_API, SOMETHING_WENT_WRONG_ERROR, LOGIN_METHODS_API, } from "../constants"; -let connectionURI; /* * Tests. */ @@ -60,23 +57,23 @@ describe("SuperTokens Multitenancy dynamic login methods", function () { let browser; let page; let pageCrashed; + let appId; before(async function () { + await backendHook("before"); const isSupported = (await isMultitenancySupported()) && (await isMultitenancyManagementEndpointsSupported()); if (!isSupported) { this.skip(); } + + browser = await setupBrowser(); }); beforeEach(async function () { - await backendBeforeEach(); - - const startSTResp = await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + await backendHook("beforeEach"); - assert.strictEqual(startSTResp.status, 200); - connectionURI = await startSTResp.text(); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); page = await browser.newPage(); pageCrashed = false; @@ -94,27 +91,14 @@ describe("SuperTokens Multitenancy dynamic login methods", function () { }); afterEach(async function () { - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } - }); - - before(async () => { - browser = await setupBrowser(); + await page?.close(); + await backendHook("afterEach"); }); after(async function () { - if (browser !== undefined) { - await browser.close(); - } + await browser?.close(); + await backendHook("after"); }); it("Renders correct signup form with emailpassword when core list of providers is empty", async function () { @@ -923,6 +907,9 @@ describe("SuperTokens Multitenancy dynamic login methods", function () { }); it("should be able to log in with dynamically added tp providers", async function () { + // This test is particularly slow, overriding the timeout to be slightly longer than default + this.timeout(40000); + await setEnabledRecipes(page, ["thirdparty"]); await enableDynamicLoginMethods(page, { emailPassword: { enabled: false }, diff --git a/test/end-to-end/multitenancy.mock.test.js b/test/end-to-end/multitenancy.mock.test.js index 34b56121f..92888edb9 100644 --- a/test/end-to-end/multitenancy.mock.test.js +++ b/test/end-to-end/multitenancy.mock.test.js @@ -18,8 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; -import fetch from "isomorphic-fetch"; import { waitForSTElement, screenshotOnFailure, @@ -29,9 +27,13 @@ import { assertProviders, getProviderLogoCount, setupBrowser, + backendHook, + setupCoreApp, + setupST, + isMultitenancySupported, + isMultitenancyManagementEndpointsSupported, } from "../helpers"; import { TEST_CLIENT_BASE_URL, DEFAULT_WEBSITE_BASE_PATH, ST_ROOT_SELECTOR } from "../constants"; -import { before } from "mocha"; /* * Tests. @@ -41,7 +43,22 @@ describe.skip("SuperTokens Multitenancy w/ mocked login methods", function () { let page; let pageCrashed; + before(async function () { + await backendHook("before"); + const isSupported = (await isMultitenancySupported()) && (await isMultitenancyManagementEndpointsSupported()); + if (!isSupported) { + this.skip(); + } + + browser = await setupBrowser(); + }); + beforeEach(async function () { + await backendHook("beforeEach"); + + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); + page = await browser.newPage(); pageCrashed = false; page.on("console", (c) => { @@ -59,17 +76,13 @@ describe.skip("SuperTokens Multitenancy w/ mocked login methods", function () { afterEach(async function () { await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } - }); - - before(async () => { - browser = await setupBrowser(); + await page?.close(); + await backendHook("afterEach"); }); after(async function () { - await browser.close(); + await browser?.close(); + await backendHook("after"); }); it("Renders correct signup form with emailpassword only when list of providers is empty", async function () { diff --git a/test/end-to-end/multitenancy.tenant_interactions.test.js b/test/end-to-end/multitenancy.tenant_interactions.test.js index bceeaedc7..b9cc54357 100644 --- a/test/end-to-end/multitenancy.tenant_interactions.test.js +++ b/test/end-to-end/multitenancy.tenant_interactions.test.js @@ -18,8 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; -import fetch from "isomorphic-fetch"; import { screenshotOnFailure, getSignInOrSignUpSwitchLink, @@ -33,7 +31,6 @@ import { getSubmitFormButton, waitForSTElement, getPasswordlessDevice, - setPasswordlessFlowType, getSessionHandleWithFetch, getLatestURLWithToken, sendEmailResetPasswordSuccessMessage, @@ -45,14 +42,15 @@ import { addUserToTenant, removeUserFromTenant, removeTenant, - backendBeforeEach, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; import { TEST_CLIENT_BASE_URL, DEFAULT_WEBSITE_BASE_PATH, - TEST_SERVER_BASE_URL, SOMETHING_WENT_WRONG_ERROR, ST_ROOT_SELECTOR, TEST_APPLICATION_SERVER_BASE_URL, @@ -66,19 +64,24 @@ describe("SuperTokens Multitenancy tenant interactions", function () { let page; let pageCrashed; + const appConfig = {}; + before(async function () { + await backendHook("before"); const isSupported = (await isMultitenancySupported()) && (await isMultitenancyManagementEndpointsSupported()); if (!isSupported) { this.skip(); } + + browser = await setupBrowser(); }); beforeEach(async function () { - await backendBeforeEach(); + await backendHook("beforeEach"); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); page = await browser.newPage(); @@ -105,26 +108,13 @@ describe("SuperTokens Multitenancy tenant interactions", function () { afterEach(async function () { await screenshotOnFailure(this, browser); - if (page) { - await page.close(); - } - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - before(async () => { - browser = await setupBrowser(); + await page?.close(); + await backendHook("afterEach"); }); after(async function () { - if (browser !== undefined) { - await browser.close(); - } + await browser?.close(); + await backendHook("after"); }); describe("without user sharing", () => { @@ -688,7 +678,11 @@ describe("SuperTokens Multitenancy tenant interactions", function () { }); it("should revoke magic links on removed tenants", async function () { - await setPasswordlessFlowType("EMAIL_OR_PHONE", "USER_INPUT_CODE_AND_MAGIC_LINK"); + await setupST({ + ...appConfig, + passwordlessContactMethod: "EMAIL_OR_PHONE", + passwordlessFlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + }); await setEnabledRecipes(page, ["passwordless"]); await setupTenant("customer1", { emailPassword: { enabled: false }, @@ -731,7 +725,11 @@ describe("SuperTokens Multitenancy tenant interactions", function () { describe("passwordless sign in", () => { beforeEach(async () => { - await setPasswordlessFlowType("EMAIL_OR_PHONE", "USER_INPUT_CODE_AND_MAGIC_LINK"); + await setupST({ + ...appConfig, + passwordlessContactMethod: "EMAIL_OR_PHONE", + passwordlessFlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + }); }); it("should work using OTP on the public tenants", async function () { diff --git a/test/end-to-end/oauth2provider.test.js b/test/end-to-end/oauth2provider.test.js index e1b2e5a67..883369c85 100644 --- a/test/end-to-end/oauth2provider.test.js +++ b/test/end-to-end/oauth2provider.test.js @@ -18,12 +18,10 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, toggleSignInSignUp, screenshotOnFailure, - backendBeforeEach, waitForUrl, createOAuth2Client, setOAuth2ClientInfo, @@ -41,16 +39,11 @@ import { isOauth2Supported, setupBrowser, getGeneralError, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; - -import { - TEST_CLIENT_BASE_URL, - TEST_SERVER_BASE_URL, - SIGN_OUT_API, - TEST_APPLICATION_SERVER_BASE_URL, - SOMETHING_WENT_WRONG_ERROR, -} from "../constants"; +import { TEST_CLIENT_BASE_URL, TEST_APPLICATION_SERVER_BASE_URL, SOMETHING_WENT_WRONG_ERROR } from "../constants"; // We do no thave to use a separate domain for the oauth2 client, since the way we are testing // the lib doesn't interact with the supertokens session handling. @@ -63,50 +56,27 @@ describe("SuperTokens OAuth2Provider", function () { let browser; let page; let consoleLogs = []; - let skipped = false; before(async function () { + await backendHook("before"); // Skip these tests if running in React 16 if (isReact16() || !(await isOauth2Supported())) { - skipped = true; this.skip(); } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - headers: { "content-type": "application/json" }, - body: JSON.stringify({ - coreConfig: { - access_token_validity: 5, // 5 seconds - }, - }), - }).catch(console.error); + const coreUrl = await setupCoreApp({ + coreConfig: { + access_token_validity: 5, // 5 seconds + }, + }); + await setupST({ coreUrl }); browser = await setupBrowser(); }); - after(async function () { - if (skipped) { - return; - } - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { + await backendHook("beforeEach"); + page = await browser.newPage(); page.on("console", (consoleObj) => { const log = consoleObj.text(); @@ -117,6 +87,17 @@ describe("SuperTokens OAuth2Provider", function () { consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("Generic OAuth2 Client Library", function () { afterEach(async function () { await removeOAuth2ClientInfo(page); diff --git a/test/end-to-end/passwordless.test_gen.js b/test/end-to-end/passwordless.test_gen.js index cab2cfaee..a7eb6040a 100644 --- a/test/end-to-end/passwordless.test_gen.js +++ b/test/end-to-end/passwordless.test_gen.js @@ -18,8 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; -import fetch from "isomorphic-fetch"; import { clearBrowserCookiesWithoutAffectingConsole, getPasswordlessDevice, @@ -27,21 +25,20 @@ import { waitForSTElement, waitFor, getFeatureFlags, - waitForText, screenshotOnFailure, - setPasswordlessFlowType, - isReact16, isGeneralErrorSupported, setGeneralErrorToLocalStorage, getInputField, isAccountLinkingSupported, - backendBeforeEach, waitForUrl, setupBrowser, clickForgotPasswordLink, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SOMETHING_WENT_WRONG_ERROR } from "../constants"; +import { TEST_CLIENT_BASE_URL, SOMETHING_WENT_WRONG_ERROR } from "../constants"; import { tryEmailPasswordSignUp, tryPasswordlessSignInUp } from "./mfa.helpers"; const examplePhoneNumber = "+36701231212"; @@ -71,6 +68,7 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe let browser; let page; let consoleLogs = []; + const signInUpPageLoadLogs = [ `ST_LOGS ${logId} OVERRIDE GET_LOGIN_ATTEMPT_INFO`, `ST_LOGS ${logId} OVERRIDE GET_LOGIN_ATTEMPT_INFO`, @@ -130,22 +128,27 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe const contactMethod = "EMAIL_OR_PHONE"; before(async function () { - ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe)); - await setPasswordlessFlowType(contactMethod, "USER_INPUT_CODE"); + const coreUrl = await setupCoreApp({ + coreConfig: { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }, + }); + await setupST({ + coreUrl, + passwordlessFlowType: "USER_INPUT_CODE", + passwordlessContactMethod: contactMethod, + }); + + ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe, undefined)); if (authRecipe === "all") { await tryEmailPasswordSignUp(page, registeredEmailWithPass); } }); after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await browser?.close(); + await backendHook("after"); }); beforeEach(async function () { @@ -329,23 +332,37 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe authRecipe === "all" && inputName == "email" ? contactInfoSubmitLogsWithEmailChecks : contactInfoSubmitLogsWithoutEmailChecks; + let accountLinkingSupported; + let coreUrl; + const coreConfig = { + passwordless_code_lifetime: 4000, + passwordless_max_code_input_attempts: 3, + }; + + before(async function () { + coreUrl = await setupCoreApp({ + coreConfig, + }); + }); + describe(`UserInputCode`, function () { + this.timeout(60000); + before(async function () { - ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe)); - await setPasswordlessFlowType(contactMethod, "USER_INPUT_CODE"); + await backendHook("before"); + await setupST({ + coreUrl, + passwordlessFlowType: "USER_INPUT_CODE", + passwordlessContactMethod: contactMethod, + }); + ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe, undefined)); accountLinkingSupported = await isAccountLinkingSupported(); }); after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await browser?.close(); + await backendHook("after"); }); beforeEach(async function () { @@ -872,20 +889,19 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe describe(`Link`, function () { before(async function () { - ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe)); - await setPasswordlessFlowType(contactMethod, "MAGIC_LINK"); + await backendHook("before"); + ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe, undefined)); accountLinkingSupported = await isAccountLinkingSupported(); + await setupST({ + coreUrl, + passwordlessFlowType: "MAGIC_LINK", + passwordlessContactMethod: contactMethod, + }); }); after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await browser?.close(); + await backendHook("after"); }); beforeEach(async function () { @@ -1356,20 +1372,19 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe describe(`Link/Code`, function () { before(async function () { - ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe)); - await setPasswordlessFlowType(contactMethod, "USER_INPUT_CODE_AND_MAGIC_LINK"); + await backendHook("before"); + await setupST({ + coreUrl, + passwordlessFlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + passwordlessContactMethod: contactMethod, + }); + ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe, undefined)); accountLinkingSupported = await isAccountLinkingSupported(); }); after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await browser?.close(); + await backendHook("after"); }); beforeEach(async function () { @@ -1871,22 +1886,21 @@ export function getPasswordlessTestCases({ authRecipe, logId, generalErrorRecipe if (authRecipe === "all") { describe("with emailpassword combo", () => { before(async function () { - ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe)); - await setPasswordlessFlowType(contactMethod, "USER_INPUT_CODE"); + await backendHook("before"); + await setupST({ + coreUrl, + passwordlessFlowType: "USER_INPUT_CODE", + passwordlessContactMethod: contactMethod, + }); + ({ browser, page } = await initBrowser(contactMethod, consoleLogs, authRecipe, undefined)); if (authRecipe === "all") { await tryEmailPasswordSignUp(page, registeredEmailWithPass); } }); after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await browser?.close(); + await backendHook("after"); }); beforeEach(async function () { @@ -1947,19 +1961,6 @@ async function setupDevice(page, inputName, contactInfo, forLinkOnly = true, cle } async function initBrowser(contactMethod, consoleLogs, authRecipe, { defaultCountry } = {}) { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - coreConfig: { - passwordless_code_lifetime: 4000, - passwordless_max_code_input_attempts: 3, - }, - }), - }).catch(console.error); - const browser = await setupBrowser(); const page = await browser.newPage(); page.on("console", (consoleObj) => { @@ -1980,11 +1981,15 @@ async function initBrowser(contactMethod, consoleLogs, authRecipe, { defaultCoun await new Promise((res) => setTimeout(res, 500)); - await tryPasswordlessSignInUp(page, exampleEmail, undefined, false); - await clearBrowserCookiesWithoutAffectingConsole(page, []); + if (["EMAIL", "EMAIL_OR_PHONE"].includes(contactMethod)) { + await tryPasswordlessSignInUp(page, exampleEmail, undefined, false); + await clearBrowserCookiesWithoutAffectingConsole(page, []); + } - await tryPasswordlessSignInUp(page, examplePhoneNumber, undefined, true); - await clearBrowserCookiesWithoutAffectingConsole(page, []); + if (["PHONE", "EMAIL_OR_PHONE"].includes(contactMethod)) { + await tryPasswordlessSignInUp(page, examplePhoneNumber, undefined, true); + await clearBrowserCookiesWithoutAffectingConsole(page, []); + } return { browser, page }; } diff --git a/test/end-to-end/refresherrors.test.js b/test/end-to-end/refresherrors.test.js index 259ef4c1f..3d8194818 100644 --- a/test/end-to-end/refresherrors.test.js +++ b/test/end-to-end/refresherrors.test.js @@ -17,15 +17,12 @@ * Imports */ import assert from "assert"; -import puppeteer from "puppeteer"; - import { clearBrowserCookiesWithoutAffectingConsole, getTextInDashboardNoAuth, screenshotOnFailure, setupBrowser, } from "../helpers"; - import { TEST_CLIENT_BASE_URL } from "../constants"; describe("Refresh errors", function () { diff --git a/test/end-to-end/resetpasswordusingtoken.test.js b/test/end-to-end/resetpasswordusingtoken.test.js index d8ebe60b6..b7a5d9851 100644 --- a/test/end-to-end/resetpasswordusingtoken.test.js +++ b/test/end-to-end/resetpasswordusingtoken.test.js @@ -18,15 +18,7 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; -import fetch from "isomorphic-fetch"; -import { - EMAIL_EXISTS_API, - RESET_PASSWORD_API, - RESET_PASSWORD_TOKEN_API, - TEST_CLIENT_BASE_URL, - TEST_SERVER_BASE_URL, -} from "../constants"; +import { EMAIL_EXISTS_API, RESET_PASSWORD_API, RESET_PASSWORD_TOKEN_API, TEST_CLIENT_BASE_URL } from "../constants"; import { clearBrowserCookiesWithoutAffectingConsole, getInputAdornmentsError, @@ -48,14 +40,14 @@ import { toggleSignInSignUp, defaultSignUp, screenshotOnFailure, - getAuthPageHeaderText, getTitleBackButton, - waitForSTElement, getResetPasswordSuccessBackToSignInButton, - backendBeforeEach, waitForText, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; /* @@ -67,52 +59,48 @@ describe("SuperTokens Reset password", function () { let consoleLogs; before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - + await backendHook("before"); browser = await setupBrowser(); - - page = await browser.newPage(); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); // Sign Up first. + page = await browser.newPage(); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), page.waitForNavigation({ waitUntil: "networkidle0" }), ]); await toggleSignInSignUp(page); await defaultSignUp(page); + page.close(); }); - after(async function () { - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); + beforeEach(async function () { + await backendHook("beforeEach"); + page = await browser.newPage(); + page.on("console", (consoleObj) => { + const log = consoleObj.text(); + // console.log(log); + if (log.startsWith("ST_LOGS")) { + consoleLogs.push(log); + } + }); + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); + }); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); }); - afterEach(function () { - return screenshotOnFailure(this, browser); + after(async function () { + await browser?.close(); + await backendHook("after"); }); describe("Reset password enter email form test", function () { beforeEach(async function () { - page = await browser.newPage(); - consoleLogs = []; - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); - } - }); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth/reset-password`), page.waitForNavigation({ waitUntil: "networkidle0" }), @@ -236,16 +224,6 @@ describe("SuperTokens Reset password", function () { describe("Reset password new password form test", function () { beforeEach(async function () { - page = await browser.newPage(); - // Catch console.log sent from PRE API HOOKS. - consoleLogs = []; - page.on("console", (consoleObj) => { - const log = consoleObj.text(); - if (log.startsWith("ST_LOGS")) { - consoleLogs.push(log); - } - }); - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); await page.goto(`${TEST_CLIENT_BASE_URL}/auth/reset-password?token=TOKEN`); }); diff --git a/test/end-to-end/routing.test.js b/test/end-to-end/routing.test.js index 27dd56570..f2736ebae 100644 --- a/test/end-to-end/routing.test.js +++ b/test/end-to-end/routing.test.js @@ -18,9 +18,6 @@ */ import assert from "assert"; -import { spawn } from "child_process"; -import puppeteer from "puppeteer"; - import { TEST_CLIENT_BASE_URL, DEFAULT_WEBSITE_BASE_PATH } from "../constants"; import { getSubmitFormButtonLabel, diff --git a/test/end-to-end/signin-rrdv5.test.js b/test/end-to-end/signin-rrdv5.test.js index 97fc6c666..6164b05f4 100644 --- a/test/end-to-end/signin-rrdv5.test.js +++ b/test/end-to-end/signin-rrdv5.test.js @@ -18,7 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, clickForgotPasswordLink, @@ -49,15 +48,15 @@ import { screenshotOnFailure, waitForText, waitForSTElement, - backendBeforeEach, setEnabledRecipes, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; import { SOMETHING_WENT_WRONG_ERROR } from "../constants"; - -import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_OUT_API } from "../constants"; +import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, SIGN_OUT_API } from "../constants"; /* * Tests. @@ -72,34 +71,14 @@ describe("SuperTokens SignIn with react router dom v5", function () { this.skip(); } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - + await backendHook("beforeEach"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); }); - after(async function () { - if (browser) { - await browser.close(); - } - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { + backendHook("beforeEach"); page = await browser.newPage(); // we set react-router-domv5 to true in localstorage await Promise.all([ @@ -119,6 +98,17 @@ describe("SuperTokens SignIn with react router dom v5", function () { consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("SignIn test ", function () { it("Should contain email and password fields only", async function () { const inputNames = await getInputNames(page); @@ -651,16 +641,26 @@ describe("SuperTokens SignIn => Server Error", function () { let page; let consoleLogs; + const appConfig = { + enabledRecipes: ["emailpassword"], + enabledProviders: [], + }; + before(async function () { - await setEnabledRecipes(["emailpassword"], []); + await backendHook("before"); + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); browser = await setupBrowser(); }); after(async function () { + await backendHook("after"); await browser.close(); }); beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); page.on("console", (consoleObj) => { @@ -677,8 +677,21 @@ describe("SuperTokens SignIn => Server Error", function () { { name: "password", value: "Str0ngP@ssw0rd" }, ]); - await submitForm(page); + await page.setRequestInterception(true); + page.on("request", (request) => { + if (request.url() === SIGN_IN_API && request.method() === "POST") { + request.respond({ + // Previous behavior was a result of core being shut down + // Emulating the same here + status: 500, + // body: "Error: No SuperTokens core available to query", + }); + } else { + request.continue(); + } + }); + await submitForm(page); await page.waitForResponse((response) => { return response.url() === SIGN_IN_API && response.status() === 500; }); diff --git a/test/end-to-end/signin-rrdv6.test.js b/test/end-to-end/signin-rrdv6.test.js index 5222f2e62..a868708e1 100644 --- a/test/end-to-end/signin-rrdv6.test.js +++ b/test/end-to-end/signin-rrdv6.test.js @@ -18,7 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, clickForgotPasswordLink, @@ -49,16 +48,16 @@ import { screenshotOnFailure, waitForText, waitForSTElement, - backendBeforeEach, getInvalidClaimsJSON, expectErrorThrown, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; import { SOMETHING_WENT_WRONG_ERROR } from "../constants"; - -import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_OUT_API } from "../constants"; +import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, SIGN_OUT_API } from "../constants"; /* * Tests. @@ -69,32 +68,14 @@ describe("SuperTokens SignIn with react router dom v6", function () { let consoleLogs = []; before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - + await backendHook("beforeEach"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); }); - after(async function () { - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { + backendHook("beforeEach"); page = await browser.newPage(); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), @@ -112,6 +93,17 @@ describe("SuperTokens SignIn with react router dom v6", function () { consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("SignIn test ", function () { it("Should contain email and password fields only", async function () { const inputNames = await getInputNames(page); @@ -800,14 +792,12 @@ describe("SuperTokens SignIn => Server Error", function () { let consoleLogs; before(async function () { + await backendHook("before"); browser = await setupBrowser(); }); - after(async function () { - await browser.close(); - }); - beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); page.on("console", (consoleObj) => { @@ -818,14 +808,38 @@ describe("SuperTokens SignIn => Server Error", function () { }); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + it("Server Error shows Something went wrong general error", async function () { await setInputValues(page, [ { name: "email", value: "john.doe@supertokens.io" }, { name: "password", value: "Str0ngP@ssw0rd" }, ]); - await submitForm(page); + await page.setRequestInterception(true); + page.on("request", (request) => { + if (request.url() === SIGN_IN_API && request.method() === "POST") { + request.respond({ + // Previous behavior was a result of core being shut down + // Emulating the same here + status: 500, + // body: "Error: No SuperTokens core available to query", + }); + } else { + request.continue(); + } + }); + await submitForm(page); await page.waitForResponse((response) => { return response.url() === SIGN_IN_API && response.status() === 500; }); diff --git a/test/end-to-end/signin.test.js b/test/end-to-end/signin.test.js index a410e3f08..6f6db2a23 100644 --- a/test/end-to-end/signin.test.js +++ b/test/end-to-end/signin.test.js @@ -18,7 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, clickForgotPasswordLink, @@ -52,16 +51,16 @@ import { setGeneralErrorToLocalStorage, getInvalidClaimsJSON as getInvalidClaims, waitForText, - backendBeforeEach, getInputField, isReact16, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; -import fetch from "isomorphic-fetch"; import { SOMETHING_WENT_WRONG_ERROR } from "../constants"; - -import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_OUT_API } from "../constants"; +import { EMAIL_EXISTS_API, SIGN_IN_API, TEST_CLIENT_BASE_URL, SIGN_OUT_API } from "../constants"; /* * Tests. @@ -72,32 +71,14 @@ describe("SuperTokens SignIn", function () { let consoleLogs = []; before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - + await backendHook("before"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); }); - after(async function () { - await browser.close(); - - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { + backendHook("beforeEach"); page = await browser.newPage(); page.on("console", (consoleObj) => { const log = consoleObj.text(); @@ -109,6 +90,17 @@ describe("SuperTokens SignIn", function () { consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + describe("SignIn test ", function () { afterEach(async function () { await page.evaluate(() => localStorage.removeItem("signoutOnSessionNotExists")); @@ -901,14 +893,12 @@ describe("SuperTokens SignIn => Server Error", function () { let consoleLogs; before(async function () { + await backendHook("before"); browser = await setupBrowser(); }); - after(async function () { - await browser.close(); - }); - beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); page.on("console", (consoleObj) => { @@ -922,14 +912,38 @@ describe("SuperTokens SignIn => Server Error", function () { await page.evaluate(() => localStorage.removeItem("TRANSLATED_GENERAL_ERROR")); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + it("Server Error shows Something went wrong network error", async function () { await setInputValues(page, [ { name: "email", value: "john.doe@supertokens.io" }, { name: "password", value: "Str0ngP@ssw0rd" }, ]); - await submitForm(page); + await page.setRequestInterception(true); + page.on("request", (request) => { + if (request.url() === SIGN_IN_API && request.method() === "POST") { + request.respond({ + // Previous behavior was a result of core being shut down + // Emulating the same here + status: 500, + // body: "Error: No SuperTokens core available to query", + }); + } else { + request.continue(); + } + }); + await submitForm(page); await page.waitForResponse((response) => { return response.url() === SIGN_IN_API && response.status() === 500; }); diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 2d4f1389b..ac43927cf 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -18,8 +18,6 @@ */ import assert from "assert"; -import fetch from "isomorphic-fetch"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, getLabelsText, @@ -38,22 +36,17 @@ import { screenshotOnFailure, getGeneralError, waitForSTElement, - backendBeforeEach, setSelectDropdownValue, getInputField, isReact16, getDefaultSignUpFieldValues, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; - -import { - EMAIL_EXISTS_API, - SIGN_UP_API, - SOMETHING_WENT_WRONG_ERROR, - TEST_CLIENT_BASE_URL, - TEST_SERVER_BASE_URL, -} from "../constants"; +import { EMAIL_EXISTS_API, SIGN_UP_API, SOMETHING_WENT_WRONG_ERROR, TEST_CLIENT_BASE_URL } from "../constants"; /* * Tests. @@ -64,11 +57,9 @@ describe("SuperTokens SignUp", function () { let consoleLogs; before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + await backendHook("before"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); page = await browser.newPage(); @@ -80,27 +71,24 @@ describe("SuperTokens SignUp", function () { }); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { + await backendHook("beforeEach"); consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); await toggleSignInSignUp(page); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); + }); + + after(async function () { + await page?.close(); + await browser?.close(); + await backendHook("after"); + }); + describe("redirectToAuth test", function () { it("Show signin first", async function () { await Promise.all([ @@ -878,14 +866,14 @@ describe("SuperTokens SignUp => Server Error", function () { let consoleLogs; before(async function () { + await backendHook("before"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); }); - after(async function () { - await browser.close(); - }); - beforeEach(async function () { + await backendHook("beforeEach"); page = await browser.newPage(); consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, []); page.on("console", (consoleObj) => { @@ -897,8 +885,14 @@ describe("SuperTokens SignUp => Server Error", function () { await toggleSignInSignUp(page); }); - afterEach(function () { - return screenshotOnFailure(this, browser); + after(async function () { + await browser.close(); + await backendHook("after"); + }); + + afterEach(async function () { + screenshotOnFailure(this, browser); + await backendHook("afterEach"); }); it("Server Error shows Something went wrong general error", async function () { @@ -908,6 +902,21 @@ describe("SuperTokens SignUp => Server Error", function () { { name: "name", value: "John Doe" }, { name: "age", value: "20" }, ]); + + await page.setRequestInterception(true); + page.on("request", (request) => { + if (request.url() === SIGN_UP_API && request.method() === "POST") { + request.respond({ + // Previous behavior was a result of core being shut down + // Emulating the same here + status: 500, + // body: "Error: No SuperTokens core available to query", + }); + } else { + request.continue(); + } + }); + await submitForm(page); await page.waitForResponse((response) => { @@ -926,6 +935,21 @@ describe("SuperTokens SignUp => Server Error", function () { { name: "name", value: "John Doe" }, { name: "age", value: "20" }, ]); + + await page.setRequestInterception(true); + page.on("request", (request) => { + if (request.url() === SIGN_UP_API && request.method() === "POST") { + request.respond({ + // Previous behavior was a result of core being shut down + // Emulating the same here + status: 500, + // body: "Error: No SuperTokens core available to query", + }); + } else { + request.continue(); + } + }); + await submitForm(page); await page.waitForResponse((response) => { diff --git a/test/end-to-end/thirdparty.test.js b/test/end-to-end/thirdparty.test.js index 0a2d131ef..86be7a4fd 100644 --- a/test/end-to-end/thirdparty.test.js +++ b/test/end-to-end/thirdparty.test.js @@ -18,26 +18,23 @@ */ import assert from "assert"; -import fetch from "isomorphic-fetch"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, assertProviders, - assertNoSTComponents, generateState, clickOnProviderButton, loginWithMockProvider, getGeneralError, - waitFor, screenshotOnFailure, clickOnProviderButtonWithoutWaiting, - backendBeforeEach, waitForUrl, setupBrowser, loginWithAuth0, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; - -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_IN_UP_API, GET_AUTH_URL_API } from "../constants"; +import { TEST_CLIENT_BASE_URL, SIGN_IN_UP_API, GET_AUTH_URL_API } from "../constants"; describe("SuperTokens Third Party", function () { getThirdPartyTestCases({ @@ -56,11 +53,9 @@ export function getThirdPartyTestCases({ authRecipe, rid, signInUpPageLoadLogs, const logId = "THIRD_PARTY"; before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + await backendHook("before"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); page = await browser.newPage(); @@ -73,15 +68,10 @@ export function getThirdPartyTestCases({ authRecipe, rid, signInUpPageLoadLogs, await page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=${authRecipe}`); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + beforeEach(async function () { + await backendHook("beforeEach"); + consoleLogs = []; + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); }); afterEach(async function () { @@ -89,12 +79,13 @@ export function getThirdPartyTestCases({ authRecipe, rid, signInUpPageLoadLogs, localStorage.removeItem("thirdPartyRedirectURL"); localStorage.removeItem("clientType"); }); - return screenshotOnFailure(this, browser); + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); }); - beforeEach(async function () { - consoleLogs = []; - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); + after(async function () { + await browser?.close(); + await backendHook("after"); }); describe("Third Party test", function () { diff --git a/test/end-to-end/thirdpartyemailpassword.test.js b/test/end-to-end/thirdpartyemailpassword.test.js index d4a9660a3..4bf2545c1 100644 --- a/test/end-to-end/thirdpartyemailpassword.test.js +++ b/test/end-to-end/thirdpartyemailpassword.test.js @@ -18,8 +18,6 @@ */ import assert from "assert"; -import fetch from "isomorphic-fetch"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, assertProviders, @@ -42,24 +40,23 @@ import { getFieldErrors, clickOnProviderButtonWithoutWaiting, getFeatureFlags, - setEnabledRecipes, - backendBeforeEach, setSelectDropdownValue, getInputField, getLabelsText, isReact16, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; import { TEST_CLIENT_BASE_URL, - TEST_SERVER_BASE_URL, SIGN_IN_UP_API, SIGN_UP_API, SOMETHING_WENT_WRONG_ERROR, EMAIL_EXISTS_API, GET_AUTH_URL_API, - TEST_APPLICATION_SERVER_BASE_URL, SIGN_IN_API, } from "../constants"; @@ -71,12 +68,13 @@ describe("SuperTokens Third Party Email Password", function () { let page; let consoleLogs; - before(async function () { - await backendBeforeEach(); + const appConfig = {}; - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + before(async function () { + await backendHook("before"); + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); browser = await setupBrowser(); page = await browser.newPage(); @@ -89,22 +87,18 @@ describe("SuperTokens Third Party Email Password", function () { }); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); }); - afterEach(function () { - return screenshotOnFailure(this, browser); + after(async function () { + await browser?.close(); + await backendHook("after"); }); beforeEach(async function () { + await backendHook("beforeEach"); consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); await Promise.all([ @@ -376,7 +370,11 @@ describe("SuperTokens Third Party Email Password", function () { this.skip(); } await assertProviders(page); - await setEnabledRecipes(["thirdparty"], []); + await setupST({ + ...appConfig, + enabledRecipes: ["thirdparty"], + enabledProviders: [], + }); await Promise.all([ page.waitForResponse( diff --git a/test/end-to-end/thirdpartypasswordless.test.js b/test/end-to-end/thirdpartypasswordless.test.js index 36e0376eb..6bbe25e28 100644 --- a/test/end-to-end/thirdpartypasswordless.test.js +++ b/test/end-to-end/thirdpartypasswordless.test.js @@ -18,7 +18,6 @@ */ import assert from "assert"; -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, clickOnProviderButton, @@ -29,18 +28,18 @@ import { submitForm, waitForSTElement, getPasswordlessDevice, - setPasswordlessFlowType, getFeatureFlags, - isReact16, assertProviders, - setEnabledRecipes, clickOnProviderButtonWithoutWaiting, getGeneralError, - backendBeforeEach, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, + screenshotOnFailure, } from "../helpers"; -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_IN_UP_API, GET_AUTH_URL_API } from "../constants"; +import { TEST_CLIENT_BASE_URL, SIGN_IN_UP_API, GET_AUTH_URL_API } from "../constants"; /* * Tests. @@ -57,7 +56,10 @@ describe("SuperTokens Third Party Passwordless", function () { "ST_LOGS PASSWORDLESS OVERRIDE GET_LOGIN_ATTEMPT_INFO", ]; + const appConfig = {}; + before(async function () { + backendHook("before"); const features = await getFeatureFlags(); if (!features.includes("passwordless")) { this.skip(); @@ -66,11 +68,9 @@ describe("SuperTokens Third Party Passwordless", function () { describe("Recipe combination tests", () => { before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); browser = await setupBrowser(); page = await browser.newPage(); @@ -82,21 +82,15 @@ describe("SuperTokens Third Party Passwordless", function () { }); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - beforeEach(async function () { + await backendHook("beforeEach"); consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - await setPasswordlessFlowType("EMAIL_OR_PHONE", "USER_INPUT_CODE_AND_MAGIC_LINK"); + await setupST({ + ...appConfig, + passwordlessContactMethod: "EMAIL_OR_PHONE", + passwordlessFlowType: "USER_INPUT_CODE_AND_MAGIC_LINK", + }); await Promise.all([ page.goto( `${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartypasswordless&passwordlessContactMethodType=EMAIL_OR_PHONE` @@ -107,11 +101,23 @@ describe("SuperTokens Third Party Passwordless", function () { afterEach(async function () { await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); + }); + + after(async function () { + await page?.close(); + await browser?.close(); + await backendHook("after"); }); it("No account consolidation", async function () { // 1. Sign up with credentials - await setPasswordlessFlowType("EMAIL_OR_PHONE", "USER_INPUT_CODE"); + await setupST({ + ...appConfig, + passwordlessContactMethod: "EMAIL_OR_PHONE", + passwordlessFlowType: "USER_INPUT_CODE", + }); await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth`), @@ -153,7 +159,11 @@ describe("SuperTokens Third Party Passwordless", function () { }); it("Successful signin with passwordless w/ required email verification", async function () { - await setPasswordlessFlowType("EMAIL_OR_PHONE", "USER_INPUT_CODE"); + await setupST({ + ...appConfig, + passwordlessContactMethod: "EMAIL_OR_PHONE", + passwordlessFlowType: "USER_INPUT_CODE", + }); await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); await Promise.all([ @@ -207,7 +217,11 @@ describe("SuperTokens Third Party Passwordless", function () { this.skip(); } await assertProviders(page); - await setEnabledRecipes(["thirdparty"], []); + await setupST({ + ...appConfig, + enabledRecipes: ["thirdparty"], + enabledProviders: [], + }); await Promise.all([ page.waitForResponse( diff --git a/test/end-to-end/thirdpartypasswordless.tp.test.js b/test/end-to-end/thirdpartypasswordless.tp.test.js index 4d919cde0..00de5017c 100644 --- a/test/end-to-end/thirdpartypasswordless.tp.test.js +++ b/test/end-to-end/thirdpartypasswordless.tp.test.js @@ -17,28 +17,7 @@ * Imports */ -import assert from "assert"; -import puppeteer from "puppeteer"; -import { - clearBrowserCookiesWithoutAffectingConsole, - clickOnProviderButton, - getUserIdWithFetch, - getLogoutButton, - loginWithMockProvider, - setInputValues, - submitForm, - waitForSTElement, - getPasswordlessDevice, - setPasswordlessFlowType, - getFeatureFlags, - isReact16, - assertProviders, - setEnabledRecipes, - clickOnProviderButtonWithoutWaiting, - getGeneralError, - backendBeforeEach, -} from "../helpers"; -import { TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL, SIGN_IN_UP_API, GET_AUTH_URL_API } from "../constants"; +import { getFeatureFlags } from "../helpers"; import { getThirdPartyTestCases } from "./thirdparty.test"; /* diff --git a/test/end-to-end/userContext.test.js b/test/end-to-end/userContext.test.js index 91b6a16e7..f8808dd0c 100644 --- a/test/end-to-end/userContext.test.js +++ b/test/end-to-end/userContext.test.js @@ -13,7 +13,6 @@ * under the License. */ -import puppeteer from "puppeteer"; import { clearBrowserCookiesWithoutAffectingConsole, getLatestURLWithToken, @@ -23,19 +22,15 @@ import { submitFormReturnRequestAndResponse, assertProviders, clickOnProviderButton, - loginWithMockProvider, - backendBeforeEach, waitForUrl, setupBrowser, loginWithAuth0, + backendHook, + setupCoreApp, + setupST, + screenshotOnFailure, } from "../helpers"; -import { - TEST_CLIENT_BASE_URL, - TEST_SERVER_BASE_URL, - RESET_PASSWORD_TOKEN_API, - RESET_PASSWORD_API, - SIGN_IN_UP_API, -} from "../constants"; +import { TEST_CLIENT_BASE_URL, RESET_PASSWORD_TOKEN_API, RESET_PASSWORD_API, SIGN_IN_UP_API } from "../constants"; import assert from "assert"; describe("SuperTokens userContext with UI components test", function () { @@ -44,11 +39,9 @@ describe("SuperTokens userContext with UI components test", function () { let consoleLogs = []; before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + backendHook("before"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); page = await browser.newPage(); @@ -60,18 +53,8 @@ describe("SuperTokens userContext with UI components test", function () { }); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - beforeEach(async function () { + await backendHook("beforeEach"); consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); await Promise.all([ @@ -80,6 +63,17 @@ describe("SuperTokens userContext with UI components test", function () { ]); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); + }); + + after(async function () { + await page?.close(); + await browser?.close(); + await backendHook("after"); + }); + it("Test that user context gets passed correctly when resetting password", async function () { await Promise.all([ page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartyemailpassword&mode=OFF&forUserContext=true`), diff --git a/test/end-to-end/userroles.test.js b/test/end-to-end/userroles.test.js index 96bb1648d..55b7941e9 100644 --- a/test/end-to-end/userroles.test.js +++ b/test/end-to-end/userroles.test.js @@ -17,8 +17,6 @@ * Imports */ import assert from "assert"; -import puppeteer from "puppeteer"; -import fetch from "isomorphic-fetch"; import { clearBrowserCookiesWithoutAffectingConsole, screenshotOnFailure, @@ -30,15 +28,17 @@ import { getInvalidClaimsJSON as getInvalidClaims, waitFor, waitForText, - backendBeforeEach, waitForUrl, setupBrowser, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; - -import { TEST_APPLICATION_SERVER_BASE_URL, TEST_CLIENT_BASE_URL, TEST_SERVER_BASE_URL } from "../constants"; +import { TEST_APPLICATION_SERVER_BASE_URL, TEST_CLIENT_BASE_URL } from "../constants"; describe("User Roles in the frontend", function () { before(async function () { + backendHook("before"); const isRolesSupported = await isUserRolesSupported(); if (!isRolesSupported) { this.skip(); @@ -49,11 +49,8 @@ describe("User Roles in the frontend", function () { let browser; let page; before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); page = await browser.newPage(); @@ -66,24 +63,8 @@ describe("User Roles in the frontend", function () { await page.close(); }); - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - afterEach(async function () { - await screenshotOnFailure(this, browser); - if (page) { - page.close(); - } - }); - beforeEach(async function () { + backendHook("beforeEach"); page = await browser.newPage(); await clearBrowserCookiesWithoutAffectingConsole(page, []); @@ -100,6 +81,17 @@ describe("User Roles in the frontend", function () { await page.waitForSelector(".sessionInfo-user-id"); }); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await page?.close(); + await backendHook("afterEach"); + }); + + after(async function () { + await browser?.close(); + await backendHook("after"); + }); + it("should be able to read in the frontend", async () => { assert.deepStrictEqual( await page.evaluate(() => diff --git a/test/end-to-end/webauthn.accountlinking.test.js b/test/end-to-end/webauthn.accountlinking.test.js new file mode 100644 index 000000000..4e42a3956 --- /dev/null +++ b/test/end-to-end/webauthn.accountlinking.test.js @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2022, SuperTokens.com + * All rights reserved. + */ + +import { TEST_CLIENT_BASE_URL } from "../constants"; +import { + setupBrowser, + screenshotOnFailure, + clearBrowserCookiesWithoutAffectingConsole, + toggleSignInSignUp, + getTestEmail, + waitForSTElement, + submitForm, + setInputValues, + getPasswordlessDevice, + waitForUrl, + changeEmail, + getLatestURLWithToken, + getUserIdWithFetch, + submitFormUnsafe, + backendHook, + setupCoreApp, + setupST, + isWebauthnSupported, +} from "../helpers"; +import { + openRecoveryAccountPage, + tryWebauthnSignUp, + getTokenFromEmail, + openRecoveryWithToken, + tryWebauthnSignIn, +} from "./webauthn.helpers"; +import { tryPasswordlessSignInUp } from "../helpers"; +import assert from "assert"; + +/* + * Test case: + * 1. The app has account linking disabled + * 2. A user signs up using a non-webauthn factor (e.g.: passwordless) + * 3. The user now tries signing up with webauthn using the same email + * -> this should work and create an entirely separate user with an unverified email address + */ +describe("SuperTokens WebAuthn Account Linking", function () { + let browser; + let page; + let consoleLogs = []; + let skipped = false; + const appConfig = { + enabledRecipes: [ + "webauthn", + "emailpassword", + "session", + "dashboard", + "userroles", + "multifactorauth", + "passwordless", + "emailverification", + "accountlinking", + ], + }; + + before(async function () { + if (!(await isWebauthnSupported())) { + skipped = true; + this.skip(); + } + + await backendHook("before"); + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); + + browser = await setupBrowser(); + page = await browser.newPage(); + page.on("console", (consoleObj) => { + const log = consoleObj.text(); + if (log.startsWith("ST_LOGS")) { + consoleLogs.push(log); + } + }); + }); + + after(async function () { + if (skipped) { + return; + } + + await page?.close(); + await browser?.close(); + await backendHook("after"); + }); + + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); + }); + + beforeEach(async function () { + await backendHook("beforeEach"); + consoleLogs = []; + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); + await toggleSignInSignUp(page); + }); + + it("Should create separate users when signing up with same email using different auth methods (account linking disabled)", async function () { + // Disable account linking + await setupST({ + ...appConfig, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: false, + shouldRequireVerification: false, + }, + }, + }); + const email = await getTestEmail(); + + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=passwordless`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + // Signup using the email + await setInputValues(page, [{ name: "email", value: email }]); + await submitForm(page); + + await waitForSTElement(page, "[data-supertokens~=input][name=userInputCode]"); + + const loginAttemptInfo = JSON.parse( + await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo")) + ); + const device = await getPasswordlessDevice(loginAttemptInfo); + await setInputValues(page, [{ name: "userInputCode", value: device.codes[0].userInputCode }]); + await submitForm(page); + await page.waitForTimeout(2000); + + // We want to parse the text inside the session-context-userId div + const userId1 = await page.evaluate(() => document.querySelector(".session-context-userId").textContent); + + // Find the div with classname logoutButton and click it using normal + // puppeteer selector + const logoutButton = await page.waitForSelector("div.logoutButton"); + await logoutButton.click(); + await new Promise((res) => setTimeout(res, 1000)); + + await tryWebauthnSignUp(page, email); + + // We should be in the confirmation page now. + await submitForm(page); + await page.waitForTimeout(4000); + + // Extract second userId from console logs + const userId2 = await page.evaluate(() => document.querySelector(".session-context-userId").textContent); + + // Verify that two different users were created + assert.notStrictEqual( + userId1, + userId2, + "Different auth methods with same email should create separate users when account linking is disabled" + ); + }); + + it("should handle email updates correctly for user that signed up with webauthn", async () => { + await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); + await setupST({ + ...appConfig, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: false, + shouldRequireVerification: false, + }, + }, + }); + const email = await getTestEmail(); + + await tryWebauthnSignUp(page, email); + + // We should be in the confirmation page now. + await submitForm(page); + + await waitForUrl(page, "/auth/verify-email"); + + // we wait for email to be created + await new Promise((r) => setTimeout(r, 1000)); + + // we fetch the email verification link and go to that + const latestURLWithToken = await getLatestURLWithToken(); + await Promise.all([page.waitForNavigation({ waitUntil: "networkidle0" }), page.goto(latestURLWithToken)]); + + // click on the continue button + await Promise.all([submitForm(page), page.waitForNavigation({ waitUntil: "networkidle0" })]); + await waitForUrl(page, "/dashboard"); + + await page.waitForTimeout(4000); + + // Change the email for the webauthn user + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + const recipeUserId = await getUserIdWithFetch(page); + assert.ok(recipeUserId); + + // Find the div with classname logoutButton and click it using normal + // puppeteer selector + const logoutButton = await page.waitForSelector("div.logoutButton"); + await logoutButton.click(); + await new Promise((res) => setTimeout(res, 1000)); + + const newEmail = getTestEmail("new"); + const res = await changeEmail("webauthn", recipeUserId, newEmail, null); + + // Sign in with the new email + await tryWebauthnSignIn(page); + + // Since mode is required, user should be redirected to verify email + // screen as the email was changed and the new email is not verified. + await waitForUrl(page, "/auth/verify-email"); + + await page.waitForTimeout(4000); + }); + + it("should allow same emails to be linked but requiring verification", async () => { + await page.evaluate(() => window.localStorage.setItem("mode", "REQUIRED")); + await setupST({ + ...appConfig, + accountLinkingConfig: { + enabled: true, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + }, + }, + }); + const email = await getTestEmail(); + + await tryPasswordlessSignInUp(page, email); + await page.waitForTimeout(1000); + + await Promise.all([page.waitForSelector(".sessionInfo-user-id"), page.waitForNetworkIdle()]); + const userId1 = await getUserIdWithFetch(page); + assert.ok(userId1); + + await page.waitForTimeout(1000); + + // Find the div with classname logoutButton and click it using normal + // puppeteer selector + const logoutButton = await page.waitForSelector("div.logoutButton"); + await logoutButton.click(); + await new Promise((res) => setTimeout(res, 1000)); + + // Try to signup with the same email through webauthn now + await tryWebauthnSignUp(page, email); + + // We should be in the confirmation page now. + await submitForm(page); + + await page.waitForTimeout(1000); + await waitForSTElement(page, "[data-supertokens~='generalError']"); + + // Try to recover the webauthn account using the same email + await openRecoveryAccountPage(page, email, true); + await page.waitForTimeout(1000); + + // Get the token from the email + const token = await getTokenFromEmail(email); + assert.ok(token); + + // Use the token to recover the account + await openRecoveryWithToken(page, token); + + // We should be in the recovery page now, click the continue button + await submitFormUnsafe(page); + + await new Promise((res) => setTimeout(res, 2000)); + + const successContainer = await waitForSTElement(page, "[data-supertokens~='headerText']"); + const headerText = await successContainer.evaluate((el) => el.textContent); + + // Assert the text contains "Account recovered successfully!" + assert.deepStrictEqual(headerText, "Account recovered successfully!"); + }); +}); diff --git a/test/end-to-end/webauthn.recover_account.test.js b/test/end-to-end/webauthn.recover_account.test.js index 8682692b5..4fbbe63dc 100644 --- a/test/end-to-end/webauthn.recover_account.test.js +++ b/test/end-to-end/webauthn.recover_account.test.js @@ -1,7 +1,4 @@ -import fetch from "isomorphic-fetch"; -import { TEST_SERVER_BASE_URL } from "../constants"; import { - backendBeforeEach, setupBrowser, screenshotOnFailure, clearBrowserCookiesWithoutAffectingConsole, @@ -9,6 +6,9 @@ import { waitForSTElement, submitFormUnsafe, isWebauthnSupported, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; import { openRecoveryWithToken, signUpAndSendRecoveryEmail, getTokenFromEmail } from "./webauthn.helpers"; import assert from "assert"; @@ -25,11 +25,10 @@ describe("SuperTokens Webauthn Recover Account", () => { skipped = true; this.skip(); } - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + await backendHook("before"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); page = await browser.newPage(); @@ -45,21 +44,18 @@ describe("SuperTokens Webauthn Recover Account", () => { if (skipped) { return; } - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await page?.close(); + await browser?.close(); + await backendHook("after"); }); - afterEach(function () { - return screenshotOnFailure(this, browser); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); }); beforeEach(async function () { + await backendHook("beforeEach"); consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); diff --git a/test/end-to-end/webauthn.recovery_email.test.js b/test/end-to-end/webauthn.recovery_email.test.js index 644f335f6..f6943c49d 100644 --- a/test/end-to-end/webauthn.recovery_email.test.js +++ b/test/end-to-end/webauthn.recovery_email.test.js @@ -1,7 +1,5 @@ -import fetch from "isomorphic-fetch"; -import { TEST_SERVER_BASE_URL } from "../constants"; import { - backendBeforeEach, + backendHook, setupBrowser, screenshotOnFailure, clearBrowserCookiesWithoutAffectingConsole, @@ -9,6 +7,8 @@ import { waitForSTElement, getTestEmail, isWebauthnSupported, + setupCoreApp, + setupST, } from "../helpers"; import { openRecoveryAccountPage, signUpAndSendRecoveryEmail, getTokenFromEmail } from "./webauthn.helpers"; import assert from "assert"; @@ -24,11 +24,10 @@ describe("SuperTokens Webauthn Recovery Email", () => { skipped = true; this.skip(); } - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); + await backendHook("before"); + const coreUrl = await setupCoreApp(); + await setupST({ coreUrl }); browser = await setupBrowser(); page = await browser.newPage(); @@ -44,21 +43,18 @@ describe("SuperTokens Webauthn Recovery Email", () => { if (skipped) { return; } - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await page?.close(); + await browser?.close(); + await backendHook("after"); }); - afterEach(function () { - return screenshotOnFailure(this, browser); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); }); beforeEach(async function () { + await backendHook("beforeEach"); consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); await toggleSignInSignUp(page); diff --git a/test/end-to-end/webauthn.signin.test.js b/test/end-to-end/webauthn.signin.test.js index 93d0335ac..cc10db8cc 100644 --- a/test/end-to-end/webauthn.signin.test.js +++ b/test/end-to-end/webauthn.signin.test.js @@ -1,17 +1,16 @@ -import fetch from "isomorphic-fetch"; -import { TEST_SERVER_BASE_URL } from "../constants"; import { - backendBeforeEach, setupBrowser, screenshotOnFailure, clearBrowserCookiesWithoutAffectingConsole, toggleSignInSignUp, - setEnabledRecipes, waitForSTElement, submitFormUnsafe, waitForUrl, getUserIdFromSessionContext, isWebauthnSupported, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; import { tryWebauthnSignIn } from "./webauthn.helpers"; import assert from "assert"; @@ -21,19 +20,20 @@ describe("SuperTokens Webauthn SignIn", () => { let page; let consoleLogs = []; let skipped = false; + const appConfig = { + enabledRecipes: ["webauthn", "emailpassword", "session", "dashboard", "userroles", "multifactorauth"], + }; before(async function () { if (!(await isWebauthnSupported())) { skipped = true; this.skip(); } - await backendBeforeEach(); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - await setEnabledRecipes(["webauthn", "emailpassword", "session", "dashboard", "userroles", "multifactorauth"]); + await backendHook("before"); + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); browser = await setupBrowser(); page = await browser.newPage(); @@ -49,21 +49,18 @@ describe("SuperTokens Webauthn SignIn", () => { if (skipped) { return; } - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await page?.close(); + await browser?.close(); + await backendHook("after"); }); - afterEach(function () { - return screenshotOnFailure(this, browser); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); }); beforeEach(async function () { + await backendHook("beforeEach"); consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); await toggleSignInSignUp(page); diff --git a/test/end-to-end/webauthn.signup.test.js b/test/end-to-end/webauthn.signup.test.js index 32b650128..5690db4e3 100644 --- a/test/end-to-end/webauthn.signup.test.js +++ b/test/end-to-end/webauthn.signup.test.js @@ -1,7 +1,4 @@ -import fetch from "isomorphic-fetch"; -import { TEST_SERVER_BASE_URL } from "../constants"; import { - backendBeforeEach, setupBrowser, screenshotOnFailure, clearBrowserCookiesWithoutAffectingConsole, @@ -9,11 +6,13 @@ import { getTestEmail, waitForSTElement, submitFormUnsafe, - setEnabledRecipes, setInputValues, isWebauthnSupported, waitForUrl, getUserIdFromSessionContext, + backendHook, + setupCoreApp, + setupST, } from "../helpers"; import { tryWebauthnSignUp, openWebauthnSignUp } from "./webauthn.helpers"; import assert from "assert"; @@ -23,6 +22,9 @@ describe("SuperTokens Webauthn SignUp", () => { let page; let consoleLogs = []; let skipped = false; + const appConfig = { + enabledRecipes: ["webauthn", "emailpassword", "session", "dashboard", "userroles", "multifactorauth"], + }; before(async function () { if (!(await isWebauthnSupported())) { @@ -30,13 +32,10 @@ describe("SuperTokens Webauthn SignUp", () => { this.skip(); } - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - await setEnabledRecipes(["webauthn", "emailpassword", "session", "dashboard", "userroles", "multifactorauth"]); + await backendHook("before"); + const coreUrl = await setupCoreApp(); + appConfig.coreUrl = coreUrl; + await setupST(appConfig); browser = await setupBrowser(); page = await browser.newPage(); @@ -53,21 +52,18 @@ describe("SuperTokens Webauthn SignUp", () => { return; } - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); + await page?.close(); + await browser?.close(); + await backendHook("after"); }); - afterEach(function () { - return screenshotOnFailure(this, browser); + afterEach(async function () { + await screenshotOnFailure(this, browser); + await backendHook("afterEach"); }); beforeEach(async function () { + await backendHook("beforeEach"); consoleLogs = []; consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); await toggleSignInSignUp(page); @@ -75,7 +71,7 @@ describe("SuperTokens Webauthn SignUp", () => { describe("SignUp test", () => { it("should not show the back button and continue without passkey button if there is only one recipe", async () => { - await setEnabledRecipes(["webauthn", "multifactorauth"]); + await setupST({ ...appConfig, enabledRecipes: ["webauthn", "multifactorauth"] }); await openWebauthnSignUp(page); // Use puppeteer to check if the back button is not shown @@ -100,14 +96,7 @@ describe("SuperTokens Webauthn SignUp", () => { assert.strictEqual(continueWithoutPasskeyBtnAfterSubmit, null); // Reset the recipes after test is done - await setEnabledRecipes([ - "webauthn", - "emailpassword", - "session", - "dashboard", - "userroles", - "multifactorauth", - ]); + await setupST({ ...appConfig }); }); it("should show the create a passkey successfully", async () => { const email = await getTestEmail(); diff --git a/test/helpers.js b/test/helpers.js index 6b58fb76f..449b1159c 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -30,6 +30,7 @@ import assert from "assert"; import { appendFile } from "fs/promises"; import mkdirp from "mkdirp"; import Puppeteer from "puppeteer"; +import addContext from "mochawesome/addContext"; const SESSION_STORAGE_STATE_KEY = "supertokens-oauth-state"; @@ -930,13 +931,20 @@ export async function screenshotOnFailure(ctx, browser) { .filter((a) => a.length !== 0) .join("_"); title = title.substring(title.length - 30); - await pages[i].screenshot({ - path: path.join(screenshotRoot, testFileName, `${title}-tab_${i}-${Date.now()}.png`), - }); + + const screenshotPath = path.join(screenshotRoot, testFileName, `${title}-tab_${i}-${Date.now()}.png`); + await pages[i].screenshot({ path: screenshotPath }); + addContext(ctx, { title: "Screenshot", value: screenshotPath }); + await new Promise((r) => setTimeout(r, 500)); - await pages[i].screenshot({ - path: path.join(screenshotRoot, testFileName, `${title}-tab_${i}-delayed-${Date.now()}.png`), - }); + + const delayedScreenshotPath = path.join( + screenshotRoot, + testFileName, + `${title}-tab_${i}-delayed-${Date.now()}.png` + ); + await pages[i].screenshot({ path: delayedScreenshotPath }); + addContext(ctx, { title: "Delayed Screenshot", value: delayedScreenshotPath }); } } } @@ -953,31 +961,6 @@ export async function getPasswordlessDevice(loginAttemptInfo) { return await deviceResp.json(); } -export function setPasswordlessFlowType(contactMethod, flowType) { - return fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/test/setFlow`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - contactMethod, - flowType, - }), - }); -} - -export function setAccountLinkingConfig(enabled, shouldAutomaticallyLink, shouldRequireVerification) { - return fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/test/setAccountLinkingConfig`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - enabled, - shouldAutoLink: { - shouldAutomaticallyLink, - shouldRequireVerification, - }, - }), - }); -} - export function changeEmail(rid, recipeUserId, email, phoneNumber) { return fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/changeEmail`, { method: "POST", @@ -991,17 +974,6 @@ export function changeEmail(rid, recipeUserId, email, phoneNumber) { }); } -export function setEnabledRecipes(enabledRecipes, enabledProviders) { - return fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/test/setEnabledRecipes`, { - method: "POST", - headers: [["content-type", "application/json"]], - body: JSON.stringify({ - enabledRecipes, - enabledProviders, - }), - }); -} - export function isReact16() { return process.env.IS_REACT_16 === "true"; } @@ -1241,6 +1213,51 @@ const testProviderConfigs = { }, }; +export async function backendHook(hookType) { + const serverUrls = Array.from(new Set([TEST_SERVER_BASE_URL, TEST_APPLICATION_SERVER_BASE_URL])); + + await Promise.all( + serverUrls.map((url) => fetch(`${url}/test/${hookType}`, { method: "POST" }).catch(console.error)) + ); +} + +export async function setupCoreApp({ appId, coreConfig } = {}) { + const response = await fetch(`${TEST_SERVER_BASE_URL}/test/setup/app`, { + method: "POST", + headers: new Headers([["content-type", "application/json"]]), + body: JSON.stringify({ + appId, + coreConfig, + }), + }); + + return await response.text(); +} + +export async function setupST({ + coreUrl, + accountLinkingConfig = {}, + enabledRecipes, + enabledProviders, + passwordlessFlowType, + passwordlessContactMethod, + mfaInfo = {}, +} = {}) { + await fetch(`${TEST_APPLICATION_SERVER_BASE_URL}/test/setup/st`, { + method: "POST", + headers: new Headers([["content-type", "application/json"]]), + body: JSON.stringify({ + coreUrl, + accountLinkingConfig, + enabledRecipes, + enabledProviders, + passwordlessFlowType, + passwordlessContactMethod, + mfaInfo, + }), + }); +} + export async function backendBeforeEach() { await fetch(`${TEST_SERVER_BASE_URL}/beforeeach`, { method: "POST", @@ -1328,3 +1345,23 @@ export async function getOAuth2TokenData(page) { const tokenData = await element.evaluate((el) => el.textContent); return JSON.parse(tokenData); } + +export async function tryPasswordlessSignInUp(page, email) { + await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}/auth/?authRecipe=passwordless`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await setInputValues(page, [{ name: "email", value: email }]); + await submitForm(page); + + await waitForSTElement(page, "[data-supertokens~=input][name=userInputCode]"); + + const loginAttemptInfo = JSON.parse( + await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo")) + ); + const device = await getPasswordlessDevice(loginAttemptInfo); + await setInputValues(page, [{ name: "userInputCode", value: device.codes[0].userInputCode }]); + await submitForm(page); +} diff --git a/test/server/index.js b/test/server/index.js index 068ad5d14..bd27061d1 100644 --- a/test/server/index.js +++ b/test/server/index.js @@ -32,15 +32,12 @@ let http = require("http"); let cors = require("cors"); const morgan = require("morgan"); let { - startST, - killAllST, - setupST, - cleanST, - setKeyValueInConfig, customAuth0Provider, maxVersion, - stopST, + setupCoreApplication, + addLicense, mockThirdPartyProvider, + getCoreUrl, } = require("./utils"); let { version: nodeSDKVersion } = require("supertokens-node/lib/build/version"); const fetch = require("isomorphic-fetch"); @@ -82,6 +79,7 @@ try { WebauthnRaw = require("supertokens-node/lib/build/recipe/webauthn/recipe").default; Webauthn = require("supertokens-node/recipe/webauthn"); } catch { + console.log("Webauthn is not supported by the tested version of the node SDK"); // Webauthn is not supported by the tested version of the node SDK } @@ -136,33 +134,6 @@ const fullProviderList = [ mockThirdPartyProvider, ]; -let urlencodedParser = bodyParser.urlencoded({ limit: "20mb", extended: true, parameterLimit: 20000 }); -let jsonParser = bodyParser.json({ limit: "20mb" }); - -let app = express(); - -const originalSend = app.response.send; -app.response.send = function sendOverWrite(body) { - originalSend.call(this, body); - this.__custombody__ = body; -}; - -morgan.token("body", function (req, res) { - return JSON.stringify(req.body); -}); - -morgan.token("res-body", function (req, res) { - return typeof res.__custombody__ === "string" ? res.__custombody__ : JSON.stringify(res.__custombody__); -}); - -app.use(urlencodedParser); -app.use(jsonParser); - -app.use(morgan("[:date[iso]] :url :method :body", { immediate: true })); -app.use(morgan("[:date[iso]] :url :method :status :response-time ms - :res[content-length] :res-body")); - -app.use(cookieParser()); - const WEB_PORT = process.env.WEB_PORT || 3031; const websiteDomain = `http://localhost:${WEB_PORT}`; let latestURLWithToken = ""; @@ -218,261 +189,270 @@ const formFields = (process.env.MIN_FIELDS && []) || [ }, ]; -let connectionURI = "http://localhost:9000"; -let passwordlessConfig = {}; -let accountLinkingConfig = {}; -let enabledProviders = undefined; -let enabledRecipes = undefined; -let mfaInfo = {}; - +// Initialize ST once to ensure all endpoints work initST(); +// Add license before the server starts +(async function () { + await addLicense(); +})(); + +/** + * Create a core application and initialize ST with the required config + * @returns URL for the new core application + */ +async function setupApp({ appId, coreConfig } = {}) { + const coreAppUrl = await setupCoreApplication({ appId, coreConfig }); + console.log("Connection URI: " + coreAppUrl); -app.use( - cors({ - origin: websiteDomain, - allowedHeaders: ["content-type", ...SuperTokens.getAllCORSHeaders()], - methods: ["GET", "PUT", "POST", "DELETE"], - credentials: true, - }) -); + return coreAppUrl; +} -app.use(middleware()); +function initST({ + coreUrl = getCoreUrl(), + accountLinkingConfig = {}, + enabledRecipes, + enabledProviders, + passwordlessFlowType, + passwordlessContactMethod, + mfaInfo = {}, +} = {}) { + if (process.env.TEST_MODE) { + UserRolesRaw.reset(); + PasswordlessRaw.reset(); + if (WebauthnRaw) { + WebauthnRaw.reset(); + } + MultitenancyRaw.reset(); + AccountLinkingRaw.reset(); + UserMetadataRaw.reset(); + MultiFactorAuthRaw.reset(); + TOTPRaw.reset(); + if (OAuth2ProviderRaw) { + OAuth2ProviderRaw.reset(); + } -app.get("/ping", async (req, res) => { - res.send("success"); -}); + EmailVerificationRaw.reset(); + EmailPasswordRaw.reset(); + ThirdPartyRaw.reset(); + SessionRaw.reset(); -app.post("/startst", async (req, res) => { - try { - connectionURI = await startST(req.body); - console.log("Connection URI: " + connectionURI); - const OPAQUE_KEY_WITH_ALL_FEATURES_ENABLED = - "N2yITHflaFS4BPm7n0bnfFCjP4sJoTERmP0J=kXQ5YONtALeGnfOOe2rf2QZ0mfOh0aO3pBqfF-S0jb0ABpat6pySluTpJO6jieD6tzUOR1HrGjJO=50Ob3mHi21tQHJ"; - - await fetch(`${connectionURI}/ee/license`, { - method: "PUT", - headers: { - "content-type": "application/json; charset=utf-8", - }, - body: JSON.stringify({ - licenseKey: OPAQUE_KEY_WITH_ALL_FEATURES_ENABLED, - }), - }); - initST(); - res.send(connectionURI + ""); - } catch (err) { - console.log(err); - res.status(500).send(err.toString()); + SuperTokensRaw.reset(); } -}); - -app.post("/beforeeach", async (req, res) => { - deviceStore = new Map(); - - mfaInfo = {}; - accountLinkingConfig = {}; - passwordlessConfig = {}; - enabledProviders = undefined; - enabledRecipes = undefined; - await killAllST(); - await setupST(); - initST(); - res.send(); -}); - -app.post("/after", async (req, res) => { - await killAllST(); - await cleanST(); - res.send(); -}); + const recipeList = [ + [ + "emailverification", + EmailVerification.init({ + mode: "OPTIONAL", + emailDelivery: { + override: (oI) => { + return { + ...oI, + sendEmail: async (input) => { + latestURLWithToken = input.emailVerifyLink; + }, + }; + }, + }, + override: { + apis: (oI) => { + return { + ...oI, + generateEmailVerifyTokenPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API email verification code", + }; + } + return oI.generateEmailVerifyTokenPOST(input); + }, + verifyEmailPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API email verify", + }; + } + return oI.verifyEmailPOST(input); + }, + }; + }, + }, + }), + ], + [ + "emailpassword", + EmailPassword.init({ + override: { + apis: (oI) => { + return { + ...oI, + passwordResetPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API reset password consume", + }; + } + return oI.passwordResetPOST(input); + }, + generatePasswordResetTokenPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API reset password", + }; + } + return oI.generatePasswordResetTokenPOST(input); + }, + emailExistsGET: async function (input) { + let generalError = input.options.req.getKeyValueFromQuery("generalError"); + if (generalError === "true") { + return { + status: "GENERAL_ERROR", + message: "general error from API email exists", + }; + } + return oI.emailExistsGET(input); + }, + signUpPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API sign up", + }; + } + return oI.signUpPOST(input); + }, + signInPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + let message = "general error from API sign in"; -app.post("/stopst", async (req, res) => { - await stopST(req.body.pid); - res.send(""); -}); + if (body.generalErrorMessage !== undefined) { + message = body.generalErrorMessage; + } -// custom API that requires session verification -app.get("/sessioninfo", verifySession(), async (req, res, next) => { - let session = req.session; - const accessTokenPayload = - session.getJWTPayload !== undefined ? session.getJWTPayload() : session.getAccessTokenPayload(); + return { + status: "GENERAL_ERROR", + message, + }; + } + return oI.signInPOST(input); + }, + }; + }, + }, + signUpFeature: { + formFields, + }, + emailDelivery: { + override: (oI) => { + return { + ...oI, + sendEmail: async (input) => { + console.log(input.passwordResetLink); + latestURLWithToken = input.passwordResetLink; + }, + }; + }, + }, + }), + ], + [ + "thirdparty", + ThirdParty.init({ + signInAndUpFeature: { + providers: + enabledProviders !== undefined + ? fullProviderList.filter(({ config }) => enabledProviders.includes(config.thirdPartyId)) + : fullProviderList, + }, + override: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + authorisationUrlGET: async function (input) { + let generalErrorFromQuery = input.options.req.getKeyValueFromQuery("generalError"); + if (generalErrorFromQuery === "true") { + return { + status: "GENERAL_ERROR", + message: "general error from API authorisation url get", + }; + } - try { - const sessionData = session.getSessionData - ? await session.getSessionData() - : await session.getSessionDataFromDatabase(); - res.send({ - sessionHandle: session.getHandle(), - userId: session.getUserId(), - recipeUserId: session.getRecipeUserId().getAsString(), - accessTokenPayload, - sessionData, - }); - } catch (err) { - next(err); - } -}); + return originalImplementation.authorisationUrlGET(input); + }, + signInUpPOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API sign in up", + }; + } -app.post("/deleteUser", async (req, res) => { - const users = await SuperTokens.listUsersByAccountInfo("public", req.body); - res.send(await SuperTokens.deleteUser(users[0].id)); -}); - -app.post("/changeEmail", async (req, res) => { - let resp; - if (req.body.rid === "emailpassword") { - resp = await EmailPassword.updateEmailOrPassword({ - recipeUserId: convertToRecipeUserIdIfAvailable(req.body.recipeUserId), - email: req.body.email, - tenantIdForPasswordPolicy: req.body.tenantId, - }); - } else if (req.body.rid === "thirdparty") { - const user = await SuperTokens.getUser({ userId: req.body.recipeUserId }); - const loginMethod = user.loginMethod.find((lm) => lm.recipeUserId.getAsString() === req.body.recipeUserId); - resp = await ThirdParty.manuallyCreateOrUpdateUser( - req.body.tenantId, - loginMethod.thirdParty.id, - loginMethod.thirdParty.userId, - req.body.email, - false - ); - } else if (req.body.rid === "passwordless") { - resp = await Passwordless.updateUser({ - recipeUserId: convertToRecipeUserIdIfAvailable(req.body.recipeUserId), - email: req.body.email, - phoneNumber: req.body.phoneNumber, - }); - } - res.json(resp); -}); - -app.get("/unverifyEmail", verifySession(), async (req, res) => { - let session = req.session; - await EmailVerification.unverifyEmail(session.getRecipeUserId()); - await session.fetchAndSetClaim(EmailVerification.EmailVerificationClaim, {}); - res.send({ status: "OK" }); -}); - -app.post("/setRole", verifySession(), async (req, res) => { - let session = req.session; - await UserRoles.createNewRoleOrAddPermissions(req.body.role, req.body.permissions); - await UserRoles.addRoleToUser(session.getTenantId(), session.getUserId(), req.body.role); - await session.fetchAndSetClaim(UserRoles.UserRoleClaim, {}); - await session.fetchAndSetClaim(UserRoles.PermissionClaim, {}); - res.send({ status: "OK" }); -}); - -app.post( - "/checkRole", - verifySession({ - overrideGlobalClaimValidators: async (gv, _session, userContext) => { - const res = [...gv]; - const body = await userContext._default.request.getJSONBody(); - if (body.role !== undefined) { - const info = body.role; - res.push(UserRoles.UserRoleClaim.validators[info.validator](...info.args)); - } - - if (body.permission !== undefined) { - const info = body.permission; - res.push(UserRoles.PermissionClaim.validators[info.validator](...info.args)); - } - return res; - }, - }), - async (req, res) => { - res.send({ status: "OK" }); - } -); - -app.post("/setMFAInfo", async (req, res) => { - mfaInfo = req.body; - - res.send({ status: "OK" }); -}); - -app.post("/completeFactor", verifySession(), async (req, res) => { - let session = req.session; - - await MultiFactorAuth.markFactorAsCompleteInSession(session, req.body.id); - - res.send({ status: "OK" }); -}); - -app.post("/addRequiredFactor", verifySession(), async (req, res) => { - let session = req.session; - - await MultiFactorAuth.addToRequiredSecondaryFactorsForUser(session.getUserId(), req.body.factorId); - - res.send({ status: "OK" }); -}); - -app.post("/mergeIntoAccessTokenPayload", verifySession(), async (req, res) => { - let session = req.session; - - await session.mergeIntoAccessTokenPayload(req.body); - - res.send({ status: "OK" }); -}); - -app.get("/token", async (_, res) => { - res.send({ - latestURLWithToken, - }); -}); - -app.post("/setupTenant", async (req, res) => { - const { tenantId, loginMethods, coreConfig } = req.body; - let firstFactors = []; - if (loginMethods.emailPassword?.enabled === true) { - firstFactors.push("emailpassword"); - } - if (loginMethods.passwordless?.enabled === true) { - firstFactors.push("otp-phone", "otp-email", "link-phone", "link-email"); - } - if (loginMethods.thirdParty?.enabled === true) { - firstFactors.push("thirdparty"); + return originalImplementation.signInUpPOST(input); + }, + }; + }, + }, + }), + ], + [ + "session", + Session.init({ + overwriteSessionDuringSignIn: true, + override: { + apis: function (originalImplementation) { + return { + ...originalImplementation, + signOutPOST: async (input) => { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from signout API", + }; + } + return originalImplementation.signOutPOST(input); + }, + }; + }, + }, + }), + ], + ]; + if (OAuth2Provider) { + recipeList.push(["oauth2provider", OAuth2Provider.init()]); } - let coreResp = await Multitenancy.createOrUpdateTenant(tenantId, { - firstFactors, - coreConfig, - }); - - if (loginMethods.thirdParty.providers !== undefined) { - for (const provider of loginMethods.thirdParty.providers) { - await Multitenancy.createOrUpdateThirdPartyConfig(tenantId, provider); - } + if (Webauthn) { + recipeList.push([ + "webauthn", + Webauthn.init({ + emailDelivery: { + override: (oI) => { + return { + ...oI, + sendEmail: async (input) => { + await saveWebauthnToken(input); + }, + }; + }, + }, + }), + ]); } - res.send(coreResp); -}); - -app.post("/addUserToTenant", async (req, res) => { - const { tenantId, recipeUserId } = req.body; - let coreResp = await Multitenancy.associateUserToTenant(tenantId, convertToRecipeUserIdIfAvailable(recipeUserId)); - res.send(coreResp); -}); - -app.post("/removeUserFromTenant", async (req, res) => { - const { tenantId, recipeUserId } = req.body; - let coreResp = await Multitenancy.disassociateUserFromTenant( - tenantId, - convertToRecipeUserIdIfAvailable(recipeUserId) - ); - res.send(coreResp); -}); - -app.post("/removeTenant", async (req, res) => { - const { tenantId } = req.body; - let coreResp = await Multitenancy.deleteTenant(tenantId); - res.send(coreResp); -}); - -app.post("/test/setFlow", (req, res) => { - passwordlessConfig = { - contactMethod: req.body.contactMethod, - flowType: req.body.flowType, + const passwordlessConfig = { + contactMethod: passwordlessContactMethod ?? "EMAIL_OR_PHONE", + flowType: passwordlessFlowType ?? "USER_INPUT_CODE_AND_MAGIC_LINK", emailDelivery: { override: (oI) => { return { @@ -490,640 +470,591 @@ app.post("/test/setFlow", (req, res) => { }, }, }; - initST(); - res.sendStatus(200); -}); - -app.post("/test/setAccountLinkingConfig", (req, res) => { - accountLinkingConfig = { - ...req.body, - }; - initST(); - res.sendStatus(200); -}); -app.post("/test/setEnabledRecipes", (req, res) => { - enabledRecipes = req.body.enabledRecipes; - enabledProviders = req.body.enabledProviders; - initST(); - res.sendStatus(200); -}); + recipeList.push([ + "passwordless", + Passwordless.init({ + ...passwordlessConfig, + override: { + apis: (originalImplementation) => { + return { + ...originalImplementation, + createCodePOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API create code", + }; + } + return originalImplementation.createCodePOST(input); + }, + resendCodePOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API resend code", + }; + } + return originalImplementation.resendCodePOST(input); + }, + consumeCodePOST: async function (input) { + let body = await input.options.req.getJSONBody(); + if (body.generalError === true) { + return { + status: "GENERAL_ERROR", + message: "general error from API consume code", + }; + } -app.get("/test/getDevice", (req, res) => { - res.send(deviceStore.get(req.query.preAuthSessionId)); -}); + const resp = await originalImplementation.consumeCodePOST(input); -app.post("/test/getTOTPCode", (req, res) => { - res.send(JSON.stringify({ totp: new OTPAuth.TOTP({ secret: req.body.secret, digits: 6, period: 1 }).generate() })); -}); + return resp; + }, + }; + }, + }, + }), + ]); -app.get("/test/featureFlags", (req, res) => { - const available = []; + recipeList.push(["userroles", UserRoles.init()]); - available.push("passwordless"); - available.push("thirdpartypasswordless"); - available.push("generalerror"); - available.push("userroles"); - available.push("multitenancy"); - available.push("multitenancyManagementEndpoints"); - available.push("accountlinking"); - available.push("mfa"); - available.push("recipeConfig"); - available.push("oauth2"); - available.push("accountlinking-fixes"); - if (Webauthn) { - available.push("webauthn"); + recipeList.push([ + "multitenancy", + Multitenancy.init({ + getAllowedDomainsForTenantId: (tenantId) => [ + `${tenantId}.example.com`, + websiteDomain.replace(/https?:\/\/([^:\/]*).*/, "$1"), + ], + }), + ]); + + accountLinkingConfig = { + enabled: false, + shouldAutoLink: { + shouldAutomaticallyLink: true, + shouldRequireVerification: true, + ...accountLinkingConfig?.shouldAutoLink, + }, + ...accountLinkingConfig, + }; + + if (accountLinkingConfig.enabled) { + recipeList.push([ + "accountlinking", + AccountLinking.init({ + shouldDoAutomaticAccountLinking: () => ({ + ...accountLinkingConfig.shouldAutoLink, + }), + }), + ]); } + recipeList.push([ + "multifactorauth", + MultiFactorAuth.init({ + firstFactors: mfaInfo.firstFactors, + override: { + functions: (oI) => ({ + ...oI, + getFactorsSetupForUser: async (input) => { + const res = await oI.getFactorsSetupForUser(input); + if (mfaInfo?.alreadySetup) { + return mfaInfo.alreadySetup; + } + return res; + }, + assertAllowedToSetupFactorElseThrowInvalidClaimError: async (input) => { + if (mfaInfo?.allowedToSetup) { + if (!mfaInfo.allowedToSetup.includes(input.factorId)) { + throw new Session.Error({ + type: "INVALID_CLAIMS", + message: "INVALID_CLAIMS", + payload: [ + { + id: "test", + reason: "test override", + }, + ], + }); + } + } else { + await oI.assertAllowedToSetupFactorElseThrowInvalidClaimError(input); + } + }, + getMFARequirementsForAuth: async (input) => { + const res = await oI.getMFARequirementsForAuth(input); + if (mfaInfo?.requirements) { + return mfaInfo.requirements; + } + return res; + }, + }), + apis: (oI) => ({ + ...oI, + resyncSessionAndFetchMFAInfoPUT: async (input) => { + const res = await oI.resyncSessionAndFetchMFAInfoPUT(input); - res.send({ - available, + if (res.status === "OK") { + if (mfaInfo.alreadySetup) { + res.factors.alreadySetup = [...mfaInfo.alreadySetup]; + } + } + if (mfaInfo.noContacts) { + res.emails = {}; + res.phoneNumbers = {}; + } + return res; + }, + }), + }, + }), + ]); + + recipeList.push([ + "totp", + TOTP.init({ + defaultPeriod: 1, + defaultSkew: 30, + }), + ]); + + SuperTokens.init({ + appInfo: { + appName: "SuperTokens", + apiDomain: "localhost:" + (process.env?.NODE_PORT ?? 8080), + websiteDomain, + }, + supertokens: { + connectionURI: coreUrl, + }, + debug: process.env.DEBUG === "true", + recipeList: + enabledRecipes !== undefined + ? recipeList.filter(([key]) => enabledRecipes.includes(key)).map(([_key, recipeFunc]) => recipeFunc) + : recipeList.map(([_key, recipeFunc]) => recipeFunc), }); -}); +} -app.post("/test/create-oauth2-client", async (req, res, next) => { - try { - const { client } = await OAuth2Provider.createOAuth2Client(req.body); - res.send({ client }); - } catch (e) { - next(e); +function convertToRecipeUserIdIfAvailable(id) { + if (SuperTokens.convertToRecipeUserId !== undefined) { + return SuperTokens.convertToRecipeUserId(id); } -}); + return id; +} -app.get("/test/webauthn/get-token", async (req, res) => { - const webauthn = webauthnStore.get(req.query.email); - if (!webauthn) { - res.status(404).send({ error: "Webauthn not found" }); - return; - } - res.send({ token: webauthn.token }); -}); +const getWebauthnLib = async () => { + const wasmBuffer = await readFile(__dirname + "/webauthn/webauthn.wasm"); -app.post("/test/webauthn/create-and-assert-credential", async (req, res) => { - try { - const { registerOptionsResponse, signInOptionsResponse, rpId, rpName, origin } = req.body; + // Set up the WebAssembly module instance + const go = new Go(); + const { instance } = await WebAssembly.instantiate(wasmBuffer, go.importObject); + go.run(instance); - const { createAndAssertCredential } = await getWebauthnLib(); - const credential = createAndAssertCredential(registerOptionsResponse, signInOptionsResponse, { + // Export extractURL from the global object + const createCredential = ( + registerOptions, + { userNotPresent = true, userNotVerified = true, rpId, rpName, origin } + ) => { + const registerOptionsString = JSON.stringify(registerOptions); + const result = global.createCredential( + registerOptionsString, rpId, rpName, origin, - userNotPresent: false, - userNotVerified: false, - }); + userNotPresent, + userNotVerified + ); - res.send({ credential }); - } catch (error) { - console.error("Error in create-and-assert-credential:", error); - res.status(500).send({ error: error.message }); - } -}); + if (!result) { + throw new Error("Failed to create credential"); + } -app.post("/test/webauthn/create-credential", async (req, res) => { - try { - const { registerOptionsResponse, rpId, rpName, origin } = req.body; + try { + const credential = JSON.parse(result); + return credential; + } catch (e) { + throw new Error("Failed to parse credential"); + } + }; - const { createCredential } = await getWebauthnLib(); - const credential = createCredential(registerOptionsResponse, { + const createAndAssertCredential = ( + registerOptions, + signInOptions, + { userNotPresent = false, userNotVerified = false, rpId, rpName, origin } + ) => { + const registerOptionsString = JSON.stringify(registerOptions); + const signInOptionsString = JSON.stringify(signInOptions); + + const result = global.createAndAssertCredential( + registerOptionsString, + signInOptionsString, rpId, rpName, origin, - userNotPresent: false, - userNotVerified: false, - }); + userNotPresent, + userNotVerified + ); - res.send({ credential }); - } catch (error) { - console.error("Error in create-credential:", error); - res.status(500).send({ error: error.message }); - } -}); + if (!result) { + throw new Error("Failed to create/assert credential"); + } -app.use(errorHandler()); + try { + const parsedResult = JSON.parse(result); + return { attestation: parsedResult.attestation, assertion: parsedResult.assertion }; + } catch (e) { + throw new Error("Failed to parse result"); + } + }; -app.use(async (err, req, res, next) => { - try { - console.error(err); - res.status(500).send(err); - } catch (ignored) {} -}); + return { createCredential, createAndAssertCredential }; +}; +let urlencodedParser = bodyParser.urlencoded({ limit: "20mb", extended: true, parameterLimit: 20000 }); +let jsonParser = bodyParser.json({ limit: "20mb" }); -let server = http.createServer(app); -server.listen(process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT, "0.0.0.0"); +let app = express(); -/* - * Setup and start the core when running the test application when running with the following command: - * START=true TEST_MODE=testing INSTALL_PATH=../../../supertokens-root NODE_PORT=8082 node . - * or - * npm run server - */ -(async function (shouldSpinUp) { - if (shouldSpinUp) { - console.log(`Start supertokens for test app`); - try { - await killAllST(); - await cleanST(); - } catch (e) {} +const originalSend = app.response.send; +app.response.send = function sendOverWrite(body) { + originalSend.call(this, body); + this.__custombody__ = body; +}; - await setupST(); - connectionURI = await startST(); - console.log("Connection URI: " + connectionURI); - } -})(process.env.START === "true"); +morgan.token("body", function (req, res) { + return JSON.stringify(req.body); +}); -function initST() { - if (process.env.TEST_MODE) { - mfaInfo = {}; +morgan.token("res-body", function (req, res) { + return typeof res.__custombody__ === "string" ? res.__custombody__ : JSON.stringify(res.__custombody__); +}); - UserRolesRaw.reset(); - PasswordlessRaw.reset(); - if (WebauthnRaw) { - WebauthnRaw.reset(); - } - MultitenancyRaw.reset(); - AccountLinkingRaw.reset(); - UserMetadataRaw.reset(); - MultiFactorAuthRaw.reset(); - TOTPRaw.reset(); - if (OAuth2ProviderRaw) { - OAuth2ProviderRaw.reset(); - } +app.use(urlencodedParser); +app.use(jsonParser); - EmailVerificationRaw.reset(); - EmailPasswordRaw.reset(); - ThirdPartyRaw.reset(); - SessionRaw.reset(); +app.use(morgan("[:date[iso]] :url :method :body", { immediate: true })); +app.use(morgan("[:date[iso]] :url :method :status :response-time ms - :res[content-length] :res-body")); - SuperTokensRaw.reset(); - } +app.use(cookieParser()); - const recipeList = [ - [ - "emailverification", - EmailVerification.init({ - mode: "OPTIONAL", - emailDelivery: { - override: (oI) => { - return { - ...oI, - sendEmail: async (input) => { - latestURLWithToken = input.emailVerifyLink; - }, - }; - }, - }, - override: { - apis: (oI) => { - return { - ...oI, - generateEmailVerifyTokenPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API email verification code", - }; - } - return oI.generateEmailVerifyTokenPOST(input); - }, - verifyEmailPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API email verify", - }; - } - return oI.verifyEmailPOST(input); - }, - }; - }, - }, - }), - ], - [ - "emailpassword", - EmailPassword.init({ - override: { - apis: (oI) => { - return { - ...oI, - passwordResetPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API reset password consume", - }; - } - return oI.passwordResetPOST(input); - }, - generatePasswordResetTokenPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API reset password", - }; - } - return oI.generatePasswordResetTokenPOST(input); - }, - emailExistsGET: async function (input) { - let generalError = input.options.req.getKeyValueFromQuery("generalError"); - if (generalError === "true") { - return { - status: "GENERAL_ERROR", - message: "general error from API email exists", - }; - } - return oI.emailExistsGET(input); - }, - signUpPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API sign up", - }; - } - return oI.signUpPOST(input); - }, - signInPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - let message = "general error from API sign in"; +app.use( + cors({ + origin: websiteDomain, + allowedHeaders: ["content-type", ...SuperTokens.getAllCORSHeaders()], + methods: ["GET", "PUT", "POST", "DELETE"], + credentials: true, + }) +); - if (body.generalErrorMessage !== undefined) { - message = body.generalErrorMessage; - } +app.use(middleware()); - return { - status: "GENERAL_ERROR", - message, - }; - } - return oI.signInPOST(input); - }, - }; - }, - }, - signUpFeature: { - formFields, - }, - emailDelivery: { - override: (oI) => { - return { - ...oI, - sendEmail: async (input) => { - console.log(input.passwordResetLink); - latestURLWithToken = input.passwordResetLink; - }, - }; - }, - }, - }), - ], - [ - "thirdparty", - ThirdParty.init({ - signInAndUpFeature: { - providers: - enabledProviders !== undefined - ? fullProviderList.filter(({ config }) => enabledProviders.includes(config.thirdPartyId)) - : fullProviderList, - }, - override: { - apis: (originalImplementation) => { - return { - ...originalImplementation, - authorisationUrlGET: async function (input) { - let generalErrorFromQuery = input.options.req.getKeyValueFromQuery("generalError"); - if (generalErrorFromQuery === "true") { - return { - status: "GENERAL_ERROR", - message: "general error from API authorisation url get", - }; - } +app.get("/ping", async (req, res) => { + res.send("success"); +}); - return originalImplementation.authorisationUrlGET(input); - }, - signInUpPOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API sign in up", - }; - } +app.post("/test/before", (_, res) => { + res.send(); +}); - return originalImplementation.signInUpPOST(input); - }, - }; - }, - }, - }), - ], - [ - "session", - Session.init({ - overwriteSessionDuringSignIn: true, - override: { - apis: function (originalImplementation) { - return { - ...originalImplementation, - signOutPOST: async (input) => { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from signout API", - }; - } - return originalImplementation.signOutPOST(input); - }, - }; - }, - }, - }), - ], - ]; - if (OAuth2Provider) { - recipeList.push(["oauth2provider", OAuth2Provider.init()]); +app.post("/test/beforeEach", (_, res) => { + deviceStore = new Map(); + res.send(); +}); + +app.post("/test/afterEach", (_, res) => { + res.send(); +}); + +app.post("/test/after", (_, res) => { + res.send(); +}); + +app.post("/test/setup/app", async (req, res) => { + try { + res.send(await setupApp(req.body)); + } catch (err) { + console.log(err); + res.status(500).send(err.toString()); } - if (Webauthn) { - recipeList.push([ - "webauthn", - Webauthn.init({ - emailDelivery: { - override: (oI) => { - return { - ...oI, - sendEmail: async (input) => { - await saveWebauthnToken(input); - }, - }; - }, - }, - }), - ]); +}); + +app.post("/test/setup/st", async (req, res) => { + try { + res.send(await initST(req.body)); + } catch (err) { + console.log(err); + res.status(500).send(err.toString()); } +}); - passwordlessConfig = { - contactMethod: "EMAIL_OR_PHONE", - flowType: "USER_INPUT_CODE_AND_MAGIC_LINK", - emailDelivery: { - override: (oI) => { - return { - ...oI, - sendEmail: saveCode, - }; - }, - }, - smsDelivery: { - override: (oI) => { - return { - ...oI, - sendSms: saveCode, - }; - }, +// custom API that requires session verification +app.get("/sessioninfo", verifySession(), async (req, res, next) => { + let session = req.session; + const accessTokenPayload = + session.getJWTPayload !== undefined ? session.getJWTPayload() : session.getAccessTokenPayload(); + + try { + const sessionData = session.getSessionData + ? await session.getSessionData() + : await session.getSessionDataFromDatabase(); + res.send({ + sessionHandle: session.getHandle(), + userId: session.getUserId(), + recipeUserId: session.getRecipeUserId().getAsString(), + accessTokenPayload, + sessionData, + }); + } catch (err) { + next(err); + } +}); + +app.post("/deleteUser", async (req, res) => { + const users = await SuperTokens.listUsersByAccountInfo("public", req.body); + res.send(await SuperTokens.deleteUser(users[0].id)); +}); + +app.post("/changeEmail", async (req, res) => { + let resp; + if (req.body.rid === "emailpassword") { + resp = await EmailPassword.updateEmailOrPassword({ + recipeUserId: convertToRecipeUserIdIfAvailable(req.body.recipeUserId), + email: req.body.email, + tenantIdForPasswordPolicy: req.body.tenantId, + }); + } else if (req.body.rid === "thirdparty") { + const user = await SuperTokens.getUser({ userId: req.body.recipeUserId }); + const loginMethod = user.loginMethod.find((lm) => lm.recipeUserId.getAsString() === req.body.recipeUserId); + resp = await ThirdParty.manuallyCreateOrUpdateUser( + req.body.tenantId, + loginMethod.thirdParty.id, + loginMethod.thirdParty.userId, + req.body.email, + false + ); + } else if (req.body.rid === "passwordless") { + resp = await Passwordless.updateUser({ + recipeUserId: convertToRecipeUserIdIfAvailable(req.body.recipeUserId), + email: req.body.email, + phoneNumber: req.body.phoneNumber, + }); + } + res.json(resp); +}); + +app.get("/unverifyEmail", verifySession(), async (req, res) => { + let session = req.session; + await EmailVerification.unverifyEmail(session.getRecipeUserId()); + await session.fetchAndSetClaim(EmailVerification.EmailVerificationClaim, {}); + res.send({ status: "OK" }); +}); + +app.post("/setRole", verifySession(), async (req, res) => { + let session = req.session; + await UserRoles.createNewRoleOrAddPermissions(req.body.role, req.body.permissions); + await UserRoles.addRoleToUser(session.getTenantId(), session.getUserId(), req.body.role); + await session.fetchAndSetClaim(UserRoles.UserRoleClaim, {}); + await session.fetchAndSetClaim(UserRoles.PermissionClaim, {}); + res.send({ status: "OK" }); +}); + +app.post( + "/checkRole", + verifySession({ + overrideGlobalClaimValidators: async (gv, _session, userContext) => { + const res = [...gv]; + const body = await userContext._default.request.getJSONBody(); + if (body.role !== undefined) { + const info = body.role; + res.push(UserRoles.UserRoleClaim.validators[info.validator](...info.args)); + } + + if (body.permission !== undefined) { + const info = body.permission; + res.push(UserRoles.PermissionClaim.validators[info.validator](...info.args)); + } + return res; }, - ...passwordlessConfig, - }; + }), + async (req, res) => { + res.send({ status: "OK" }); + } +); - recipeList.push([ - "passwordless", - Passwordless.init({ - ...passwordlessConfig, - override: { - apis: (originalImplementation) => { - return { - ...originalImplementation, - createCodePOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API create code", - }; - } - return originalImplementation.createCodePOST(input); - }, - resendCodePOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API resend code", - }; - } - return originalImplementation.resendCodePOST(input); - }, - consumeCodePOST: async function (input) { - let body = await input.options.req.getJSONBody(); - if (body.generalError === true) { - return { - status: "GENERAL_ERROR", - message: "general error from API consume code", - }; - } +app.post("/completeFactor", verifySession(), async (req, res) => { + let session = req.session; - const resp = await originalImplementation.consumeCodePOST(input); + await MultiFactorAuth.markFactorAsCompleteInSession(session, req.body.id); + + res.send({ status: "OK" }); +}); + +app.post("/addRequiredFactor", verifySession(), async (req, res) => { + let session = req.session; + + await MultiFactorAuth.addToRequiredSecondaryFactorsForUser(session.getUserId(), req.body.factorId); + + res.send({ status: "OK" }); +}); + +app.post("/mergeIntoAccessTokenPayload", verifySession(), async (req, res) => { + let session = req.session; + + await session.mergeIntoAccessTokenPayload(req.body); + + res.send({ status: "OK" }); +}); + +app.get("/token", async (_, res) => { + res.send({ + latestURLWithToken, + }); +}); + +app.post("/setupTenant", async (req, res) => { + const { tenantId, loginMethods, coreConfig } = req.body; + let firstFactors = []; + if (loginMethods.emailPassword?.enabled === true) { + firstFactors.push("emailpassword"); + } + if (loginMethods.passwordless?.enabled === true) { + firstFactors.push("otp-phone", "otp-email", "link-phone", "link-email"); + } + if (loginMethods.thirdParty?.enabled === true) { + firstFactors.push("thirdparty"); + } + let coreResp = await Multitenancy.createOrUpdateTenant(tenantId, { + firstFactors, + coreConfig, + }); + + if (loginMethods.thirdParty.providers !== undefined) { + for (const provider of loginMethods.thirdParty.providers) { + await Multitenancy.createOrUpdateThirdPartyConfig(tenantId, provider); + } + } + res.send(coreResp); +}); + +app.post("/addUserToTenant", async (req, res) => { + const { tenantId, recipeUserId } = req.body; + let coreResp = await Multitenancy.associateUserToTenant(tenantId, convertToRecipeUserIdIfAvailable(recipeUserId)); + res.send(coreResp); +}); + +app.post("/removeUserFromTenant", async (req, res) => { + const { tenantId, recipeUserId } = req.body; + let coreResp = await Multitenancy.disassociateUserFromTenant( + tenantId, + convertToRecipeUserIdIfAvailable(recipeUserId) + ); + res.send(coreResp); +}); - return resp; - }, - }; - }, - }, - }), - ]); +app.post("/removeTenant", async (req, res) => { + const { tenantId } = req.body; + let coreResp = await Multitenancy.deleteTenant(tenantId); + res.send(coreResp); +}); - recipeList.push(["userroles", UserRoles.init()]); +app.get("/test/getDevice", (req, res) => { + res.send(deviceStore.get(req.query.preAuthSessionId)); +}); - recipeList.push([ - "multitenancy", - Multitenancy.init({ - getAllowedDomainsForTenantId: (tenantId) => [ - `${tenantId}.example.com`, - websiteDomain.replace(/https?:\/\/([^:\/]*).*/, "$1"), - ], - }), - ]); +app.post("/test/getTOTPCode", (req, res) => { + res.send(JSON.stringify({ totp: new OTPAuth.TOTP({ secret: req.body.secret, digits: 6, period: 1 }).generate() })); +}); - accountLinkingConfig = { - enabled: false, - shouldAutoLink: { - ...accountLinkingConfig?.shouldAutoLink, - shouldAutomaticallyLink: true, - shouldRequireVerification: true, - }, - ...accountLinkingConfig, - }; - if (accountLinkingConfig.enabled) { - recipeList.push([ - "accountlinking", - AccountLinking.init({ - shouldDoAutomaticAccountLinking: () => ({ - ...accountLinkingConfig.shouldAutoLink, - }), - }), - ]); - } - recipeList.push([ - "multifactorauth", - MultiFactorAuth.init({ - firstFactors: mfaInfo.firstFactors, - override: { - functions: (oI) => ({ - ...oI, - getFactorsSetupForUser: async (input) => { - const res = await oI.getFactorsSetupForUser(input); - if (mfaInfo?.alreadySetup) { - return mfaInfo.alreadySetup; - } - return res; - }, - assertAllowedToSetupFactorElseThrowInvalidClaimError: async (input) => { - if (mfaInfo?.allowedToSetup) { - if (!mfaInfo.allowedToSetup.includes(input.factorId)) { - throw new Session.Error({ - type: "INVALID_CLAIMS", - message: "INVALID_CLAIMS", - payload: [ - { - id: "test", - reason: "test override", - }, - ], - }); - } - } else { - await oI.assertAllowedToSetupFactorElseThrowInvalidClaimError(input); - } - }, - getMFARequirementsForAuth: async (input) => { - const res = await oI.getMFARequirementsForAuth(input); - if (mfaInfo?.requirements) { - return mfaInfo.requirements; - } - return res; - }, - }), - apis: (oI) => ({ - ...oI, - resyncSessionAndFetchMFAInfoPUT: async (input) => { - const res = await oI.resyncSessionAndFetchMFAInfoPUT(input); +app.get("/test/featureFlags", (req, res) => { + const available = []; - if (res.status === "OK") { - if (mfaInfo.alreadySetup) { - res.factors.alreadySetup = [...mfaInfo.alreadySetup]; - } - } - if (mfaInfo.noContacts) { - res.emails = {}; - res.phoneNumbers = {}; - } - return res; - }, - }), - }, - }), - ]); + available.push("passwordless"); + available.push("thirdpartypasswordless"); + available.push("generalerror"); + available.push("userroles"); + available.push("multitenancy"); + available.push("multitenancyManagementEndpoints"); + available.push("accountlinking"); + available.push("mfa"); + available.push("recipeConfig"); + available.push("oauth2"); + available.push("accountlinking-fixes"); - recipeList.push([ - "totp", - TOTP.init({ - defaultPeriod: 1, - defaultSkew: 30, - }), - ]); + if (Webauthn !== undefined) { + available.push("webauthn"); + } - SuperTokens.init({ - appInfo: { - appName: "SuperTokens", - apiDomain: "localhost:" + (process.env.NODE_PORT === undefined ? 8080 : process.env.NODE_PORT), - websiteDomain, - }, - supertokens: { - connectionURI, - }, - debug: process.env.DEBUG === "true", - recipeList: - enabledRecipes !== undefined - ? recipeList.filter(([key]) => enabledRecipes.includes(key)).map(([_key, recipeFunc]) => recipeFunc) - : recipeList.map(([_key, recipeFunc]) => recipeFunc), + res.send({ + available, }); -} +}); -function convertToRecipeUserIdIfAvailable(id) { - if (SuperTokens.convertToRecipeUserId !== undefined) { - return SuperTokens.convertToRecipeUserId(id); +app.post("/test/create-oauth2-client", async (req, res, next) => { + try { + const { client } = await OAuth2Provider.createOAuth2Client(req.body); + res.send({ client }); + } catch (e) { + next(e); } - return id; -} +}); -const getWebauthnLib = async () => { - const wasmBuffer = await readFile(__dirname + "/webauthn/webauthn.wasm"); +app.get("/test/webauthn/get-token", async (req, res) => { + const webauthn = webauthnStore.get(req.query.email); + if (!webauthn) { + res.status(404).send({ error: "Webauthn not found" }); + return; + } + res.send({ token: webauthn.token }); +}); - // Set up the WebAssembly module instance - const go = new Go(); - const { instance } = await WebAssembly.instantiate(wasmBuffer, go.importObject); - go.run(instance); +app.post("/test/webauthn/create-and-assert-credential", async (req, res) => { + try { + const { registerOptionsResponse, signInOptionsResponse, rpId, rpName, origin } = req.body; - // Export extractURL from the global object - const createCredential = ( - registerOptions, - { userNotPresent = true, userNotVerified = true, rpId, rpName, origin } - ) => { - const registerOptionsString = JSON.stringify(registerOptions); - const result = global.createCredential( - registerOptionsString, + const { createAndAssertCredential } = await getWebauthnLib(); + const credential = createAndAssertCredential(registerOptionsResponse, signInOptionsResponse, { rpId, rpName, origin, - userNotPresent, - userNotVerified - ); - - if (!result) { - throw new Error("Failed to create credential"); - } + userNotPresent: false, + userNotVerified: false, + }); - try { - const credential = JSON.parse(result); - return credential; - } catch (e) { - throw new Error("Failed to parse credential"); - } - }; + res.send({ credential }); + } catch (error) { + console.error("Error in create-and-assert-credential:", error); + res.status(500).send({ error: error.message }); + } +}); - const createAndAssertCredential = ( - registerOptions, - signInOptions, - { userNotPresent = false, userNotVerified = false, rpId, rpName, origin } - ) => { - const registerOptionsString = JSON.stringify(registerOptions); - const signInOptionsString = JSON.stringify(signInOptions); +app.post("/test/webauthn/create-credential", async (req, res) => { + try { + const { registerOptionsResponse, rpId, rpName, origin } = req.body; - const result = global.createAndAssertCredential( - registerOptionsString, - signInOptionsString, + const { createCredential } = await getWebauthnLib(); + const credential = createCredential(registerOptionsResponse, { rpId, rpName, origin, - userNotPresent, - userNotVerified - ); + userNotPresent: false, + userNotVerified: false, + }); - if (!result) { - throw new Error("Failed to create/assert credential"); - } + res.send({ credential }); + } catch (error) { + console.error("Error in create-credential:", error); + res.status(500).send({ error: error.message }); + } +}); - try { - const parsedResult = JSON.parse(result); - return { attestation: parsedResult.attestation, assertion: parsedResult.assertion }; - } catch (e) { - throw new Error("Failed to parse result"); - } - }; +app.use(errorHandler()); - return { createCredential, createAndAssertCredential }; -}; +app.use(async (err, req, res, next) => { + try { + console.error(err); + res.status(500).send(err); + } catch (ignored) {} +}); + +let server = http.createServer(app); +server.listen(process.env?.NODE_PORT ?? 8080, "0.0.0.0"); diff --git a/test/server/package-lock.json b/test/server/package-lock.json index a15168c7c..0b624ae9d 100644 --- a/test/server/package-lock.json +++ b/test/server/package-lock.json @@ -1,7 +1,7 @@ { "name": "server", "version": "0.0.0", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -19,10 +19,23 @@ "supertokens-node": "^22.0.0" } }, + "node_modules/@noble/hashes": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.6.1.tgz", + "integrity": "sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -35,6 +48,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", "dependencies": { "debug": "4" }, @@ -43,11 +57,12 @@ } }, "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -59,24 +74,28 @@ } }, "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/axios": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.14.4" } @@ -98,12 +117,14 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" }, @@ -115,6 +136,7 @@ "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "license": "MIT", "dependencies": { "bytes": "3.1.0", "content-type": "~1.0.4", @@ -149,6 +171,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -157,24 +180,42 @@ "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -184,6 +225,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -195,6 +237,7 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "license": "MIT", "dependencies": { "safe-buffer": "5.1.2" }, @@ -206,6 +249,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -214,6 +258,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -222,6 +267,7 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz", "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==", + "license": "MIT", "dependencies": { "cookie": "0.3.1", "cookie-signature": "1.0.6" @@ -233,12 +279,14 @@ "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -248,48 +296,40 @@ } }, "node_modules/cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", "dependencies": { - "node-fetch": "^2.6.12" + "node-fetch": "^2.7.0" } }, "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" }, "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -298,6 +338,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -305,20 +346,37 @@ "node_modules/destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "license": "MIT" }, "node_modules/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "license": "BSD-2-Clause", "engines": { "node": ">=10" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } @@ -326,25 +384,74 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -353,6 +460,7 @@ "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "license": "MIT", "dependencies": { "accepts": "~1.3.7", "array-flatten": "1.1.1", @@ -393,6 +501,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -401,6 +510,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -415,15 +525,16 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -434,12 +545,14 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" }, "engines": { @@ -450,6 +563,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -458,6 +572,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -466,50 +581,65 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "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", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { + "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dependencies": { - "get-intrinsic": "^1.2.2" + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -517,10 +647,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -529,9 +663,10 @@ } }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -543,6 +678,7 @@ "version": "1.7.2", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "license": "MIT", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -558,6 +694,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" @@ -567,11 +704,12 @@ } }, "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -583,14 +721,16 @@ } }, "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -615,25 +755,38 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", @@ -654,20 +807,14 @@ "node_modules/jsonwebtoken/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/jssha": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", - "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==", - "engines": { - "node": "*" - } + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -678,66 +825,74 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "node_modules/libphonenumber-js": { - "version": "1.10.13", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.13.tgz", - "integrity": "sha512-b74iyWmwb4GprAUPjPkJ11GTC7KX4Pd3onpJfKxYyY8y9Rbb4ERY47LvCMEDM09WD3thiLDMXtkfDK/AX+zT7Q==" + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz", + "integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==", + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" } }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -745,12 +900,14 @@ "node_modules/merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "license": "MIT" }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -759,6 +916,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -770,6 +928,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -778,6 +937,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -789,6 +949,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", @@ -804,6 +965,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -811,12 +973,14 @@ "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -825,6 +989,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -841,9 +1006,10 @@ } }, "node_modules/nodemailer": { - "version": "6.7.8", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.8.tgz", - "integrity": "sha512-2zaTFGqZixVmTxpJRCFC+Vk5eGRd/fYtvIR+dl5u9QXLTQWGIf48x/JXvo58g9sa0bU6To04XUv554Paykum3g==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", + "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "license": "MIT-0", "engines": { "node": ">=6.0.0" } @@ -852,14 +1018,19 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -868,6 +1039,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -879,16 +1051,18 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/otpauth": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.2.0.tgz", - "integrity": "sha512-vbiHaeTJHrRG4fWRAZwVVrCnQz9SEzNINk2Hfx8BZY8UxTJEnqpOHxr11KfrRVAqWZdD6Y5jdyXW6Xp/ls9O/w==", + "version": "9.3.6", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.3.6.tgz", + "integrity": "sha512-eIcCvuEvcAAPHxUKC9Q4uCe0Fh/yRc5jv9z+f/kvyIF2LPrhgAOuLB7J9CssGYhND/BL8M9hlHBTFmffpoQlMQ==", + "license": "MIT", "dependencies": { - "jssha": "~3.3.1" + "@noble/hashes": "1.6.1" }, "funding": { "url": "https://github.com/hectorm/otpauth?sponsor=1" @@ -897,12 +1071,14 @@ "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -910,12 +1086,14 @@ "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "license": "MIT" }, "node_modules/pkce-challenge": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-3.1.0.tgz", "integrity": "sha512-bQ/0XPZZ7eX+cdAkd61uYWpfMhakH3NeteUF1R8GNa+LMqX8QFAkbCLqq+AYAns1/ueACBu/BMWhrlKGrdvGZg==", + "license": "MIT", "dependencies": { "crypto-js": "^4.1.1" } @@ -924,6 +1102,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -932,6 +1111,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -943,12 +1123,14 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" }, "node_modules/qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.6" } @@ -956,12 +1138,14 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "license": "MIT" }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -970,6 +1154,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "license": "MIT", "dependencies": { "bytes": "3.1.0", "http-errors": "1.7.2", @@ -983,30 +1168,32 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "license": "MIT" }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" }, "node_modules/scmp": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", - "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", + "license": "BSD-3-Clause" }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1018,6 +1205,7 @@ "version": "0.17.1", "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "~1.1.2", @@ -1040,12 +1228,14 @@ "node_modules/send/node_modules/ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "license": "MIT" }, "node_modules/serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "license": "MIT", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -1057,37 +1247,84 @@ } }, "node_modules/set-cookie-parser": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", - "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==" + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" }, - "node_modules/set-function-length": { + "node_modules/setprototypeof": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1097,6 +1334,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1104,11 +1342,13 @@ "node_modules/supertokens-js-override": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/supertokens-js-override/-/supertokens-js-override-0.0.4.tgz", - "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" + "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==", + "license": "Apache-2.0" }, "node_modules/supertokens-node": { "version": "22.0.0", - "resolved": "git+ssh://git@github.com/supertokens/supertokens-node.git#5761a67136636a9070c69bb649e014de6eb21403", + "resolved": "https://registry.npmjs.org/supertokens-node/-/supertokens-node-22.0.0.tgz", + "integrity": "sha512-VulJsn0IfT9eICesivSuIBbbZiM/UMINeMdxpdQzF4kbfya5kUY23sbwbuPP1nC1VfoDRByJPD2+UvROAKOUTw==", "license": "Apache-2.0", "dependencies": { "buffer": "^6.0.3", @@ -1132,16 +1372,18 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/supertokens-node/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1152,39 +1394,35 @@ } } }, - "node_modules/supertokens-node/node_modules/jose": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", - "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/supertokens-node/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/tldts": { - "version": "6.1.57", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.57.tgz", - "integrity": "sha512-Oy7yDXK8meJl8vPMOldzA+MtueAJ5BrH4l4HXwZuj2AtfoQbLjmTJmjNWPUcAo+E/ibHn7QlqMS0BOcXJFJyHQ==", + "version": "6.1.85", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.85.tgz", + "integrity": "sha512-gBdZ1RjCSevRPFix/hpaUWeak2/RNUZB4/8frF1r5uYMHjFptkiT0JXIebWvgI/0ZHXvxaUDDJshiA0j6GdL3w==", + "license": "MIT", "dependencies": { - "tldts-core": "^6.1.57" + "tldts-core": "^6.1.85" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.57", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.57.tgz", - "integrity": "sha512-lXnRhuQpx3zU9EONF9F7HfcRLvN1uRYUBIiKL+C/gehC/77XTU+Jye6ui86GA3rU6FjlJ0triD1Tkjt2F/2lEg==" + "version": "6.1.85", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.85.tgz", + "integrity": "sha512-DTjUVvxckL1fIoPSb3KE7ISNtkWSawZdpfxGxwiIrZoO6EbHVDXXUIlIuWympPaeS+BLGyggozX/HTMsRAdsoA==", + "license": "MIT" }, "node_modules/toidentifier": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -1192,12 +1430,14 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" }, "node_modules/twilio": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.19.3.tgz", - "integrity": "sha512-3X5Czl9Vg4QFl+2pnfMQ+H8YfEDQ4WeuAmqjUpbK65x0DfmxTCHuPEFWUKVZCJZew6iltJB/1whhVvIKETe54A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.23.0.tgz", + "integrity": "sha512-LdNBQfOe0dY2oJH2sAsrxazpgfFQo5yXGxe96QA8UWB5uu+433PrUbkv8gQ5RmrRCqUTPQ0aOrIyAdBr1aB03Q==", + "license": "MIT", "dependencies": { "axios": "^1.6.0", "dayjs": "^1.11.9", @@ -1213,21 +1453,23 @@ } }, "node_modules/twilio/node_modules/axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "node_modules/twilio/node_modules/qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -1240,6 +1482,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -1252,6 +1495,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1260,6 +1504,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -1269,6 +1514,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -1277,6 +1523,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1284,12 +1531,14 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -1299,988 +1548,10 @@ "version": "13.0.2", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", + "license": "MIT", "engines": { "node": ">=6.0" } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - }, - "dependencies": { - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", - "requires": { - "follow-redirects": "^1.14.4" - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, - "basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "requires": { - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" - } - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==" - }, - "cookie-parser": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz", - "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==", - "requires": { - "cookie": "0.3.1", - "cookie-signature": "1.0.6" - } - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "requires": { - "node-fetch": "^2.6.12" - } - }, - "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" - }, - "dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" - }, - "dotenv": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", - "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" - }, - "ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - } - }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" - }, - "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "requires": { - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, - "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "requires": { - "get-intrinsic": "^1.2.2" - } - }, - "has-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" - }, - "hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "requires": { - "function-bind": "^1.1.2" - } - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "requires": { - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, - "jssha": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz", - "integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==" - }, - "jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "requires": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "requires": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "libphonenumber-js": { - "version": "1.10.13", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.13.tgz", - "integrity": "sha512-b74iyWmwb4GprAUPjPkJ11GTC7KX4Pd3onpJfKxYyY8y9Rbb4ERY47LvCMEDM09WD3thiLDMXtkfDK/AX+zT7Q==" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" - } - }, - "morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "requires": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "nodemailer": { - "version": "6.7.8", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.8.tgz", - "integrity": "sha512-2zaTFGqZixVmTxpJRCFC+Vk5eGRd/fYtvIR+dl5u9QXLTQWGIf48x/JXvo58g9sa0bU6To04XUv554Paykum3g==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" - }, - "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, - "otpauth": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.2.0.tgz", - "integrity": "sha512-vbiHaeTJHrRG4fWRAZwVVrCnQz9SEzNINk2Hfx8BZY8UxTJEnqpOHxr11KfrRVAqWZdD6Y5jdyXW6Xp/ls9O/w==", - "requires": { - "jssha": "~3.3.1" - } - }, - "pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "pkce-challenge": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-3.1.0.tgz", - "integrity": "sha512-bQ/0XPZZ7eX+cdAkd61uYWpfMhakH3NeteUF1R8GNa+LMqX8QFAkbCLqq+AYAns1/ueACBu/BMWhrlKGrdvGZg==", - "requires": { - "crypto-js": "^4.1.1" - } - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" - }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "scmp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", - "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==" - }, - "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "set-cookie-parser": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", - "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==" - }, - "set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "requires": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" - } - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" - }, - "supertokens-js-override": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/supertokens-js-override/-/supertokens-js-override-0.0.4.tgz", - "integrity": "sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==" - }, - "supertokens-node": { - "version": "git+ssh://git@github.com/supertokens/supertokens-node.git#5761a67136636a9070c69bb649e014de6eb21403", - "from": "supertokens-node@^22.0.0", - "requires": { - "buffer": "^6.0.3", - "content-type": "^1.0.5", - "cookie": "^0.7.2", - "cross-fetch": "^3.1.6", - "debug": "^4.3.3", - "jose": "^4.13.1", - "libphonenumber-js": "^1.9.44", - "nodemailer": "^6.7.2", - "pako": "^2.1.0", - "pkce-challenge": "^3.0.0", - "process": "^0.11.10", - "set-cookie-parser": "^2.6.0", - "supertokens-js-override": "^0.0.4", - "tldts": "^6.1.48", - "twilio": "^4.19.3" - }, - "dependencies": { - "cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - } - }, - "jose": { - "version": "4.14.4", - "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", - "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, - "tldts": { - "version": "6.1.57", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.57.tgz", - "integrity": "sha512-Oy7yDXK8meJl8vPMOldzA+MtueAJ5BrH4l4HXwZuj2AtfoQbLjmTJmjNWPUcAo+E/ibHn7QlqMS0BOcXJFJyHQ==", - "requires": { - "tldts-core": "^6.1.57" - } - }, - "tldts-core": { - "version": "6.1.57", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.57.tgz", - "integrity": "sha512-lXnRhuQpx3zU9EONF9F7HfcRLvN1uRYUBIiKL+C/gehC/77XTU+Jye6ui86GA3rU6FjlJ0triD1Tkjt2F/2lEg==" - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" - }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "twilio": { - "version": "4.19.3", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-4.19.3.tgz", - "integrity": "sha512-3X5Czl9Vg4QFl+2pnfMQ+H8YfEDQ4WeuAmqjUpbK65x0DfmxTCHuPEFWUKVZCJZew6iltJB/1whhVvIKETe54A==", - "requires": { - "axios": "^1.6.0", - "dayjs": "^1.11.9", - "https-proxy-agent": "^5.0.0", - "jsonwebtoken": "^9.0.0", - "qs": "^6.9.4", - "scmp": "^2.1.0", - "url-parse": "^1.5.9", - "xmlbuilder": "^13.0.2" - }, - "dependencies": { - "axios": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", - "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "qs": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", - "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "xmlbuilder": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", - "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } } diff --git a/test/server/utils.js b/test/server/utils.js index 64e242071..2c884b2d4 100644 --- a/test/server/utils.js +++ b/test/server/utils.js @@ -13,89 +13,78 @@ * under the License. */ const { exec } = require("child_process"); -let fs = require("fs"); let assert = require("assert"); let axios = require("axios").default; +const { randomUUID } = require("node:crypto"); -module.exports.executeCommand = async function (cmd) { - return new Promise((resolve, reject) => { - exec(cmd, (err, stdout, stderr) => { - if (err) { - // console.log({ err }); - reject(err); - return; - } - resolve({ stdout, stderr }); - }); +module.exports.getCoreUrl = () => { + const host = process.env?.SUPERTOKENS_CORE_HOST ?? "localhost"; + const port = process.env?.SUPERTOKENS_CORE_PORT ?? "3567"; + + const coreUrl = `http://${host}:${port}`; + + return coreUrl; +}; + +module.exports.setupCoreApplication = async function ({ appId, coreConfig } = {}) { + const coreUrl = module.exports.getCoreUrl(); + + if (!appId) { + appId = randomUUID(); + } + + if (!coreConfig) { + coreConfig = {}; + } + + const createAppResp = await fetch(`${coreUrl}/recipe/multitenancy/app/v2`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + appId, + coreConfig, + }), }); + + const respBody = await createAppResp.json(); + assert.strictEqual(respBody.status, "OK"); + + return `${coreUrl}/appid-${appId}`; }; -module.exports.setupST = async function () { - let installationPath = process.env.INSTALL_PATH; - try { - await module.exports.executeCommand("cd " + installationPath + " && cp temp/licenseKey ./licenseKey"); - } catch (ignored) {} - await module.exports.executeCommand("cd " + installationPath + " && cp temp/config.yaml ./config.yaml"); +module.exports.addLicense = async function () { + const coreUrl = module.exports.getCoreUrl(); + + const OPAQUE_KEY_WITH_ALL_FEATURES_ENABLED = + "N2yITHflaFS4BPm7n0bnfFCjP4sJoTERmP0J=kXQ5YONtALeGnfOOe2rf2QZ0mfOh0aO3pBqfF-S0jb0ABpat6pySluTpJO6jieD6tzUOR1HrGjJO=50Ob3mHi21tQHJ"; + + // TODO: This should be done on the core directly, not in apps + await fetch(`${coreUrl}/ee/license`, { + method: "PUT", + headers: { + "content-type": "application/json; charset=utf-8", + }, + body: JSON.stringify({ + licenseKey: OPAQUE_KEY_WITH_ALL_FEATURES_ENABLED, + }), + }); }; -module.exports.setKeyValueInConfig = async function (key, value) { +module.exports.executeCommand = async function (cmd) { return new Promise((resolve, reject) => { - let installationPath = process.env.INSTALL_PATH; - fs.readFile(installationPath + "/config.yaml", "utf8", function (err, data) { + exec(cmd, (err, stdout, stderr) => { if (err) { + // console.log({ err }); reject(err); return; } - let oldStr = new RegExp("((#\\s)?)" + key + "(:|((:\\s).+))\n"); - let newStr = key + ": " + value + "\n"; - let result = data.replace(oldStr, newStr); - fs.writeFile(installationPath + "/config.yaml", result, "utf8", function (err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); + resolve({ stdout, stderr }); }); }); }; -module.exports.cleanST = async function () { - let installationPath = process.env.INSTALL_PATH; - try { - await module.exports.executeCommand("cd " + installationPath + " && rm licenseKey"); - } catch (ignored) {} - await module.exports.executeCommand("cd " + installationPath + " && rm -f config.yaml"); - await module.exports.executeCommand("cd " + installationPath + " && rm -rf .webserver-temp-*"); - await module.exports.executeCommand("cd " + installationPath + " && rm -rf .started"); -}; - -module.exports.stopST = async function (pid) { - let pidsBefore = await getListOfPids(); - if (pidsBefore.length === 0) { - return; - } - await module.exports.executeCommand("kill " + pid); - let startTime = Date.now(); - while (Date.now() - startTime < 10000) { - let pidsAfter = await getListOfPids(); - if (pidsAfter.includes(pid)) { - await new Promise((r) => setTimeout(r, 100)); - continue; - } else { - return; - } - } - throw new Error("error while stopping ST with PID: " + pid); -}; - -module.exports.killAllST = async function () { - let pids = await getListOfPids(); - for (let i = 0; i < pids.length; i++) { - await module.exports.stopST(pids[i]); - } -}; - module.exports.startST = async function (config = {}) { const host = config.host ?? "localhost"; const port = config.port ?? 9000; diff --git a/test/updateExampleAppDeps.sh b/test/updateExampleAppDeps.sh index be8602111..4ea3f129d 100755 --- a/test/updateExampleAppDeps.sh +++ b/test/updateExampleAppDeps.sh @@ -1,7 +1,7 @@ #!/bin/bash cd $1; -npm i; +npm i --ignore-scripts; npm install git+https://github.com:supertokens/supertokens-auth-react.git#$GITHUB_SHA; if [ -d "frontend" ]; then diff --git a/test/visual/basic.test.js b/test/visual/basic.test.js deleted file mode 100644 index 432e34056..000000000 --- a/test/visual/basic.test.js +++ /dev/null @@ -1,921 +0,0 @@ -/* Copyright (c) 2021, VRAI Labs and/or its affiliates. All rights reserved. - * - * This software is licensed under the Apache License, Version 2.0 (the - * "License") as published by the Apache Software Foundation. - * - * You may not use this file except in compliance with the License. You may - * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -/* - * Imports - */ - -import assert from "assert"; -import puppeteer from "puppeteer"; -import fetch from "isomorphic-fetch"; -import { - RESET_PASSWORD_API, - RESET_PASSWORD_TOKEN_API, - SIGN_IN_API, - SIGN_IN_UP_API, - SIGN_UP_API, - TEST_CLIENT_BASE_URL, - TEST_SERVER_BASE_URL, - VERIFY_EMAIL_API, -} from "../constants"; -import { - clearBrowserCookiesWithoutAffectingConsole, - getLatestURLWithToken, - getPasswordlessDevice, - setPasswordlessFlowType, - waitFor, -} from "../helpers"; -import percySnapshot from "@percy/puppeteer"; - -// Run the tests in a DOM environment. -require("jsdom-global")(); - -describe("Visual testing", function () { - // The test "cases" depend on each, they are more like steps.. - // Meaning we should bail after the first failure - this.bail(true); - - let browser; - let page; - - let c = 0; - let titleStack = []; - const snapshot = async (page, title) => { - const fullTitle = `${titleStack.join(" - ")}: ${title}`; - // await page.screenshot({ path: `./test_report/${++c}-${fullTitle.replace(/ |:/g, "_")}.jpg` }); - await percySnapshot(page, fullTitle); - }; - - async function snapshotDuringRequest(page, { clickSelector, cb, apiPath, method, title }) { - const requestDone = new DeferredPromise(); - const requestHandler = async (request) => { - // If method called before hasMethodBeenCalled timeouts, update methodCalled. - if (request.url().endsWith(apiPath) && (method === undefined || request.method() === method)) { - await snapshot(page, title); - requestDone.resolve(); - } - request.continue(); - }; - await page.setRequestInterception(true); - page.on("request", requestHandler); - try { - if (cb) { - await cb(); - } else { - await page.click(clickSelector); - } - await requestDone.promise; - } finally { - page.off("request", requestHandler); - await page.setRequestInterception(false); - } - } - - before(async function () { - await fetch(`${TEST_SERVER_BASE_URL}/beforeeach`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - browser = await puppeteer.launch({ - args: ["--no-sandbox", "--disable-setuid-sandbox"], - headless: true, - }); - page = await browser.newPage(); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?useShadowDom=false`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - }); - - after(async function () { - await browser.close(); - await fetch(`${TEST_SERVER_BASE_URL}/after`, { - method: "POST", - }).catch(console.error); - await fetch(`${TEST_SERVER_BASE_URL}/stopst`, { - method: "POST", - }).catch(console.error); - }); - - beforeEach(function () { - titleStack = [this.currentTest.title]; - let c = this.currentTest.parent; - while (c.title !== "Visual testing") { - titleStack.unshift(c.title); - c = c.parent; - } - }); - - afterEach(function () { - titleStack = []; - }); - - describe("Email password", () => { - it("Sign up", async () => { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=emailpassword&mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~='headerSubtitle']"); - // Switch to sign up - await page.click("[data-supertokens~='headerSubtitle'] > [data-supertokens~='link']"); - await snapshot(page, "empty"); - - await setInputValue(page, { name: "email", value: "john.doe+emailpassword@supertokens.io" }); - await setInputValue(page, { name: "password", value: "weak" }); - await page.click("[name=email]"); // switch focus out of the password field to trigger validation - await snapshot(page, "weak password"); - - await setInputValue(page, { name: "password", value: "Str0ngP@ssw0rd" }); - await snapshot(page, "filled with strong password"); - - await page.click("[data-supertokens~=showPassword]"); - await snapshot(page, "filled with password shown"); - - await page.click("[data-supertokens~='button'][type=submit]"); - await snapshot(page, "missing required fields"); - - await setInputValue(page, { name: "name", value: "John Doe" }); - await setInputValue(page, { name: "age", value: "asdf" }); - await page.click("[data-supertokens~='button'][type=submit]"); - await snapshot(page, "custom field validation failure"); - - await setInputValue(page, { name: "age", value: "20" }); - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: SIGN_UP_API, - title: "during sign up call", - }); - - await page.waitForNavigation({ waitUntil: "networkidle0" }); - await clearBrowserCookiesWithoutAffectingConsole(page, []); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~='headerSubtitle']"); - // Switch to sign up - await page.click("[data-supertokens~='headerSubtitle'] > [data-supertokens~='link']"); - - await setInputValue(page, { name: "email", value: "john.doe+emailpassword@supertokens.io" }); - await setInputValue(page, { name: "password", value: "weak" }); - await snapshot(page, "email already taken"); - }); - - it("Sign in", async () => { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await snapshot(page, "empty"); - - await page.click("[data-supertokens='button']"); - await snapshot(page, "empty submit"); - - await setInputValue(page, { name: "email", value: "john.doe+emailpassword@supertokens.io" }); - await page.click("[data-supertokens='button']"); - await snapshot(page, "empty password"); - - await setInputValue(page, { name: "password", value: "weak" }); - await page.click("[data-supertokens='button']"); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "wrong password"); - - await setInputValue(page, { name: "email", value: "john.doe+emailpassword2@supertokens.io" }); - // we wait for the error to go away - await page.waitForSelector("[data-supertokens~=generalError]", { hidden: true }); - - await page.click("[data-supertokens='button']"); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "email not exists"); - - await setInputValue(page, { name: "email", value: "john.doe+emailpassword@supertokens.io" }); - await setInputValue(page, { name: "password", value: "Str0ngP@ssw0rd" }); - await page.click("[data-supertokens~=showPassword]"); - await snapshot(page, "password shown"); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: SIGN_IN_API, - title: "during sign in call", - }); - }); - - it("Email verification", async () => { - await page.waitForNavigation({ waitUntil: "networkidle0" }); - let pathname = await page.evaluate(() => window.location.pathname); - assert.deepStrictEqual(pathname, "/auth/verify-email"); - - // we wait for email to be created - await page.waitForSelector("[data-supertokens~='sendVerifyEmailIcon']"); - await snapshot(page, "verify email screen"); - - await waitFor(1000); // Make sure the link is created - - // we fetch the email verification link and go to that - const latestURLWithToken = await getLatestURLWithToken(); - - // click continue + snapshot while loading - await snapshotDuringRequest(page, { - cb: () => page.goto(latestURLWithToken), - title: "link during verify", - apiPath: VERIFY_EMAIL_API, - }); - - await page.waitForSelector("[data-supertokens='button']"); - await snapshot(page, "verification success"); - - await page.click("[data-supertokens='button']"); - - await snapshot(page, "verification success + continue"); - - await clearBrowserCookiesWithoutAffectingConsole(page, []); - await page.goto(latestURLWithToken); - await page.waitForSelector("[data-supertokens~=container]"); - await page.waitForSelector("[data-supertokens='button']"); - await snapshot(page, "requires interaction"); - await page.click("[data-supertokens='button']"); - await page.waitForSelector("[data-supertokens~=headerTinyTitle]"); - await snapshot(page, "link invalid link"); - }); - - it("Reset password", async () => { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await page.click("[data-supertokens~='forgotPasswordLink']"); - await snapshot(page, "screen empty"); - - await setInputValue(page, { name: "email", value: "john.doe" }); - await page.click("body"); - await snapshot(page, "invalid email"); - - await setInputValue(page, { name: "email", value: "john.doe+emailpassword@supertokens.io" }); - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens~=button]", - title: "during password reset submit", - apiPath: RESET_PASSWORD_TOKEN_API, - }); - - await page.waitForSelector("[data-supertokens~=enterEmailSuccessMessage]"); - await snapshot(page, "email sent"); - - await page.hover("[data-supertokens~=secondaryLinkWithLeftArrow]"); - await snapshot(page, "hover over back arrow"); - - // wait to make sure link is created - await waitFor(500); - - const latestURLWithToken = await getLatestURLWithToken(); - await page.goto(latestURLWithToken); - await page.waitForSelector("[name=confirm-password]"); - await snapshot(page, "submit new password form empty"); - - await setInputValue(page, { name: "password", value: "weak" }); - await page.click("[name=confirm-password]"); // switch focus out of the password field to trigger validation - await snapshot(page, "weak password"); - - await setInputValue(page, { name: "password", value: "Asdf12.." }); - await page.click("[data-supertokens~=showPassword]"); - await snapshot(page, "password show"); - - await setInputValue(page, { name: "confirm-password", value: "weak" }); - await page.click("[name=password]"); // switch focus out of the password field to trigger validation - await snapshot(page, "weak confirm-password"); - - await page.click("[data-supertokens~=showPassword]"); - await snapshot(page, "confirm-password show"); - - await setInputValue(page, { name: "confirm-password", value: "Mismatching12.." }); - await page.click("[data-supertokens~=button"); - await snapshot(page, "mismatching passwords"); - - await setInputValue(page, { name: "confirm-password", value: "Asdf12.." }); - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens~=button]", - title: "during new password submit", - apiPath: RESET_PASSWORD_API, - }); - await page.waitForSelector("[data-supertokens~=submitNewPasswordSuccessMessage]"); - await snapshot(page, "password reset success"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth/reset-password?rid=emailpassword&token=TASD`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await setInputValue(page, { name: "password", value: "Asdf12.." }); - await setInputValue(page, { name: "confirm-password", value: "Asdf12.." }); - await page.click("[data-supertokens~=button"); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "invalid token"); - }); - }); - - describe("Third party", () => { - it("Sign in up", async () => { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdparty`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "initial view"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=signin`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "general error"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=no_email_present`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "no_email_present error"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=custom&message=test_error_message`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "custom error"); - }); - - it("Callback screen", async () => { - await Promise.all([ - await page.click("[data-supertokens~=providerGoogle]"), - await page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdparty`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - const oauthState = JSON.parse( - await page.evaluate(() => sessionStorage.getItem("supertokens-oauth-state-2")) - ); - await snapshotDuringRequest(page, { - cb: () => - page.goto( - `${TEST_CLIENT_BASE_URL}/auth/callback/google?code=68ef254asdfasdf1234asdf1234asdf1234&state=${oauthState.stateForAuthProvider}` - ), - apiPath: SIGN_IN_UP_API, - title: "during sign in up call", - }); - }); - }); - - describe("Passwordless", () => { - it("Email form", async () => { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=passwordless&passwordlessContactMethodType=EMAIL`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "empty"); - - await setInputValue(page, { name: "email", value: "asdf" }); - await page.click("body"); - await waitFor(500); - await snapshot(page, "invalid email"); - - await setInputValue(page, { name: "email", value: "john.doe@supertokens.io" }); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: "/auth/signinup/code", - title: "during create code call", - }); - await page.waitForSelector("[name=userInputCode]"); - }); - - it("Phone form", async () => { - await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=passwordless&passwordlessContactMethodType=PHONE`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "empty"); - // emailOrPhone_text - await setInputValue(page, { name: "phoneNumber_text", value: "213456" }); - await page.click("body"); - await waitFor(500); - await snapshot(page, "invalid phone number"); - - await page.click("[data-supertokens~=phoneInputWrapper] [role=combobox]"); - await snapshot(page, "country dropdown"); - const focusedElement = await page.evaluateHandle(() => document.activeElement); - await focusedElement.type("hu"); - await snapshot(page, "country dropdown search"); - await setInputValue(page, { name: "phoneNumber_text", value: "+36701111111" }); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: "/auth/signinup/code", - title: "during create code call", - }); - await page.waitForSelector("[name=userInputCode]"); - }); - - it("Email + phone form", async () => { - await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=passwordless&passwordlessContactMethodType=EMAIL_OR_PHONE&useShadowDom=false` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "empty"); - - await setInputValue(page, { name: "emailOrPhone", value: "asdf" }); - await page.click("body"); - await waitFor(500); - await snapshot(page, "invalid email"); - - await setInputValue(page, { name: "emailOrPhone", value: "john.doe@supertokens.io" }); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: "/auth/signinup/code", - title: "during create code call with email", - }); - await page.waitForSelector("[name=userInputCode]"); - await page.evaluate(() => localStorage.clear()); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=passwordless&passwordlessContactMethodType=EMAIL_OR_PHONE&useShadowDom=false` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - await setInputValue(page, { name: "emailOrPhone", value: "213456" }); - await page.click("[data-supertokens='button']"); - await snapshot(page, "invalid phone number"); - - await page.click("[data-supertokens~=phoneInputWrapper] [role=combobox]"); - await snapshot(page, "country dropdown"); - const focusedElement = await page.evaluateHandle(() => document.activeElement); - await focusedElement.type("hu"); - await snapshot(page, "country dropdown search"); - await setInputValue(page, { name: "emailOrPhone_text", value: "+36701111111" }); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: "/auth/signinup/code", - title: "during create code call with phone number", - }); - }); - - it("OTP screen", async () => { - await page.waitForSelector("[name=userInputCode]"); - await snapshot(page, "fresh load with phone number"); - - await page.evaluate(() => localStorage.clear()); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=passwordless&passwordlessContactMethodType=EMAIL&useShadowDom=false` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - await page.waitForSelector("[name=email]"); - await setInputValue(page, { name: "email", value: "john.doe@supertokens.io" }); - - await page.click("[data-supertokens='button']"); - await page.waitForSelector("[name=userInputCode]"); - await snapshot(page, "fresh load with email"); - - await page.waitForSelector("[data-supertokens~=resendCodeBtn]:not([disabled])"); - await snapshot(page, "resend enabled"); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens~=resendCodeBtn]", - apiPath: "/auth/signinup/code/resend", - title: "during resend request", - }); - await snapshot(page, "resend success"); - - await page.click("[data-supertokens~='button'][type=submit]"); - await snapshot(page, "empty submit with success notif"); - - await page.waitForSelector("[data-supertokens~=generalSuccess]", { hidden: true }); - await snapshot(page, "empty submit"); - - await setInputValue(page, { name: "userInputCode", value: "asdf" }); - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens~='button'][type=submit]", - apiPath: "/auth/signinup/code/consume", - title: "during submit", - }); - - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "wrong OTP submitted"); - }); - - it("Link sent screen", async () => { - await page.evaluate(() => localStorage.clear()); - await setPasswordlessFlowType("EMAIL_OR_PHONE", "MAGIC_LINK"); - await page.evaluate(() => localStorage.clear()); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=passwordless&passwordlessContactMethodType=EMAIL&useShadowDom=false` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - await setInputValue(page, { name: "email", value: "john.doe@supertokens.io" }); - - await page.click("[data-supertokens=button]"); - await page.waitForSelector("[data-supertokens~=sendCodeIcon"); - - await snapshot(page, "with email"); - - await setPasswordlessFlowType("EMAIL_OR_PHONE", "MAGIC_LINK"); - await page.evaluate(() => localStorage.clear()); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=passwordless&passwordlessContactMethodType=PHONE&useShadowDom=false` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - await setInputValue(page, { name: "phoneNumber_text", value: "+36701111111" }); - - await page.click("[data-supertokens=button]"); - await page.waitForSelector("[data-supertokens~=sendCodeIcon"); - - await snapshot(page, "with phone"); - - await page.waitForSelector("[data-supertokens~=resendCodeBtn]:not([disabled])"); - await snapshot(page, "resend enabled"); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens~=resendCodeBtn]", - apiPath: "/auth/signinup/code/resend", - title: "during resend request", - }); - await page.waitForSelector("[data-supertokens~=generalSuccess]"); - await snapshot(page, "resend success"); - - await failRequest(page, { - clickSelector: "[data-supertokens~=resendCodeBtn]", - apiPath: "/auth/signinup/code/resend", - }); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "resend request error"); - }); - - it("Magic link screen", async () => { - const loginAttemptInfo = JSON.parse( - await page.evaluate(() => localStorage.getItem("supertokens-passwordless-loginAttemptInfo")) - ); - const device = await getPasswordlessDevice(loginAttemptInfo); - await snapshotDuringRequest(page, { - cb: () => page.goto(device.codes[0].urlWithLinkCode), - apiPath: "/auth/signinup/code/consume", - title: "during consume", - }); - - await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); - await page.goto(device.codes[0].urlWithLinkCode); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "requiring interaction"); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens~='button']", - apiPath: "/auth/signinup/code/consume", - title: "during consume if requires interaction", - }); - await page.waitForSelector("[data-supertokens~=generalError]"); - }); - }); - - describe("Third party + Email password", () => { - it("Password sign up", async () => { - await clearBrowserCookiesWithoutAffectingConsole(page, []); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartyemailpassword&mode=REQUIRED`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~='headerSubtitle']"); - // Switch to sign up - await page.click("[data-supertokens~='headerSubtitle'] > [data-supertokens~='link']"); - await snapshot(page, "empty"); - - await setInputValue(page, { name: "email", value: "john.doe+thirdpartyemailpassword@supertokens.io" }); - await setInputValue(page, { name: "password", value: "weak" }); - await page.click("[name=email]"); // switch focus out of the password field to trigger validation - await snapshot(page, "weak password"); - - await setInputValue(page, { name: "password", value: "Str0ngP@ssw0rd" }); - await snapshot(page, "filled with strong password"); - - await page.click("[data-supertokens~=showPassword]"); - await snapshot(page, "filled with password shown"); - - await page.click("[data-supertokens~='button'][type=submit]"); - await snapshot(page, "missing required fields"); - - await setInputValue(page, { name: "name", value: "John Doe" }); - await setInputValue(page, { name: "age", value: "asdf" }); - await page.click("[data-supertokens~='button'][type=submit]"); - await snapshot(page, "custom field validation failure"); - - await setInputValue(page, { name: "age", value: "20" }); - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: SIGN_UP_API, - title: "during sign up call", - }); - - await page.waitForNavigation({ waitUntil: "networkidle0" }); - await clearBrowserCookiesWithoutAffectingConsole(page, []); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~='headerSubtitle']"); - // Switch to sign up - await page.click("[data-supertokens~='headerSubtitle'] > [data-supertokens~='link']"); - - await setInputValue(page, { name: "email", value: "john.doe+thirdpartyemailpassword@supertokens.io" }); - await setInputValue(page, { name: "password", value: "weak" }); - await snapshot(page, "email already taken"); - }); - - it("Password sign in", async () => { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "empty"); - - await page.click("[data-supertokens='button']"); - await snapshot(page, "empty submit"); - - await setInputValue(page, { name: "email", value: "john.doe+thirdpartyemailpassword@supertokens.io" }); - await page.click("[data-supertokens='button']"); - await snapshot(page, "empty password"); - - await setInputValue(page, { name: "password", value: "weak" }); - await page.click("[data-supertokens='button']"); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "wrong password"); - - await setInputValue(page, { name: "email", value: "john.doe+thirdpartyemailpassword2@supertokens.io" }); - // we wait for the error to go away - await page.waitForSelector("[data-supertokens~=generalError]", { hidden: true }); - - await page.click("[data-supertokens='button']"); - await page.waitForSelector("[data-supertokens~=generalError]"); - await snapshot(page, "email not exists"); - - await setInputValue(page, { name: "email", value: "john.doe+thirdpartyemailpassword@supertokens.io" }); - await setInputValue(page, { name: "password", value: "Str0ngP@ssw0rd" }); - await page.click("[data-supertokens~=showPassword]"); - await snapshot(page, "password shown"); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: SIGN_IN_API, - title: "during sign in call", - }); - }); - - it("Third party Sign in up", async () => { - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartyemailpassword`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "initial view"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=signin`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "general error"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=no_email_present`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "no_email_present error"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=custom&message=test_error_message`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "custom error"); - }); - }); - - describe("Third party + Passwordless", () => { - it("Third party Sign in up", async () => { - await clearBrowserCookiesWithoutAffectingConsole(page, []); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartypasswordless`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "initial view"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=signin`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "general error"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=no_email_present`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "no_email_present error"); - - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}/auth?error=custom&message=test_error_message`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "custom error"); - }); - it("Email form", async () => { - await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartypasswordless&passwordlessContactMethodType=EMAIL` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "empty"); - - await setInputValue(page, { name: "email", value: "asdf" }); - await page.click("body"); - await waitFor(500); - await snapshot(page, "invalid email"); - - await setInputValue(page, { name: "email", value: "john.doe@supertokens.io" }); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: "/auth/signinup/code", - title: "during create code call", - }); - - await page.waitForSelector("[data-supertokens~=sendCodeIcon"); - }); - - it("Phone form", async () => { - await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartypasswordless&passwordlessContactMethodType=PHONE` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "empty"); - // emailOrPhone_text - await setInputValue(page, { name: "phoneNumber_text", value: "213456" }); - await page.click("body"); - await waitFor(500); - await snapshot(page, "invalid phone number"); - - await page.click("[data-supertokens~=phoneInputWrapper] [role=combobox]"); - await snapshot(page, "country dropdown"); - const focusedElement = await page.evaluateHandle(() => document.activeElement); - await focusedElement.type("hu"); - await snapshot(page, "country dropdown search"); - await setInputValue(page, { name: "phoneNumber_text", value: "+36701111111" }); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: "/auth/signinup/code", - title: "during create code call", - }); - await page.waitForSelector("[data-supertokens~=sendCodeIcon"); - }); - - it("Email + phone form", async () => { - await page.evaluate(() => localStorage.removeItem("supertokens-passwordless-loginAttemptInfo")); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartypasswordless&passwordlessContactMethodType=EMAIL_OR_PHONE&useShadowDom=false` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - await page.waitForSelector("[data-supertokens~=container]"); - await snapshot(page, "empty"); - - await setInputValue(page, { name: "emailOrPhone", value: "asdf" }); - await page.click("body"); - await waitFor(500); - await snapshot(page, "invalid email"); - - await setInputValue(page, { name: "emailOrPhone", value: "john.doe@supertokens.io" }); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: "/auth/signinup/code", - title: "during create code call with email", - }); - await page.waitForSelector("[data-supertokens~=sendCodeIcon"); - await page.evaluate(() => localStorage.clear()); - await Promise.all([ - page.goto( - `${TEST_CLIENT_BASE_URL}/auth?authRecipe=thirdpartypasswordless&passwordlessContactMethodType=EMAIL_OR_PHONE&useShadowDom=false` - ), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - await setInputValue(page, { name: "emailOrPhone", value: "213456" }); - await page.click("[data-supertokens='button']"); - await snapshot(page, "invalid phone number"); - - await page.click("[data-supertokens~=phoneInputWrapper] [role=combobox]"); - await snapshot(page, "country dropdown"); - const focusedElement = await page.evaluateHandle(() => document.activeElement); - await focusedElement.type("hu"); - await snapshot(page, "country dropdown search"); - await setInputValue(page, { name: "emailOrPhone_text", value: "+36701111111" }); - - await snapshotDuringRequest(page, { - clickSelector: "[data-supertokens='button']", - apiPath: "/auth/signinup/code", - title: "during create code call with phone number", - }); - await page.waitForSelector("[data-supertokens~=sendCodeIcon"); - }); - }); -}); - -async function setInputValue(page, f) { - const input = await page.waitForSelector(`[name=${f.name}]`); - await input.click({ clickCount: 3 }); - await input.type(f.value); -} - -async function failRequest(page, { clickSelector, cb, apiPath, method }) { - const requestDone = new DeferredPromise(); - const requestHandler = async (request) => { - // If method called before hasMethodBeenCalled timeouts, update methodCalled. - if (request.url().endsWith(apiPath) && (method === undefined || request.method() === method)) { - request.abort(); - requestDone.resolve(); - } else { - request.continue(); - } - }; - await page.setRequestInterception(true); - page.on("request", requestHandler); - try { - if (cb) { - await cb(); - } else { - await page.click(clickSelector); - } - await requestDone.promise; - } finally { - page.off("request", requestHandler); - await page.setRequestInterception(false); - } -} -class DeferredPromise { - constructor() { - this.promise = new Promise((res, rej) => { - this.resolve = res; - this.reject = rej; - }); - } -}